예제 #1
0
        /// <summary>
        /// Render a string from an atlas.
        /// </summary>
        /// <param name="position">The top left position of where to start drawing the string.</param>
        /// <param name="color">The text color.</param>
        /// <param name="text">The text itself.</param>
        /// <param name="atlas">The font atlas to use.</param>
        /// <param name="layouter">The layouter to use.</param>
        /// <param name="effect">Effect to apply</param>
        /// <param name="effectAmount">The effect amount.</param>
        /// <param name="effectColor">The effect color.</param>
        public void RenderString(
            Vector3 position, Color color, string text, DrawableFontAtlas atlas, TextLayouter layouter = null,
            FontEffect effect = FontEffect.None, float effectAmount = 0f, Color?effectColor = null)
        {
            layouter ??= new TextLayouter(atlas);

            atlas.SetupDrawing(this, text, effect, effectAmount, effectColor);

            var reUsableVector = new Vector3();

            foreach (char c in text)
            {
                Vector2 gPos = layouter.AddLetter(c, out DrawableGlyph g);
                if (g == null || g.GlyphUV == Rectangle.Empty)
                {
                    continue;
                }

                reUsableVector.X = gPos.X;
                reUsableVector.Y = gPos.Y;
                atlas.DrawGlyph(this, g, position + reUsableVector, color);
            }

            atlas.FinishDrawing(this);
        }
예제 #2
0
        public static unsafe void CompareMetricsWithStb(Font f, DrawableFontAtlas atlas, StbTrueType.stbtt_fontinfo stbFont)
        {
            var pc = new StbTrueType.stbtt_pack_context();

            StbTrueType.stbtt_PackBegin(pc, (byte *)0, 512, 512, 512, 1, null);

            var cd = new StbTrueType.stbtt_packedchar[f.LastCharIndex + 1];

            StbTrueType.stbrp_rect[] rects;
            fixed(StbTrueType.stbtt_packedchar *charDataPtr = &cd[0])
            {
                var range = new StbTrueType.stbtt_pack_range
                {
                    first_unicode_codepoint_in_range = 0,
                    array_of_unicode_codepoints      = null,
                    num_chars          = (int)f.LastCharIndex + 1,
                    chardata_for_range = charDataPtr,
                    font_size          = -atlas.FontSize
                };

                rects = new StbTrueType.stbrp_rect[f.LastCharIndex + 1];
                fixed(StbTrueType.stbrp_rect *rectPtr = &rects[0])
                {
                    int n = StbTrueType.stbtt_PackFontRangesGatherRects(pc, stbFont, &range, 1, rectPtr);

                    StbTrueType.stbtt_PackFontRangesPackRects(pc, rectPtr, n);
                }
            }

            foreach ((char charIndex, DrawableGlyph atlasGlyph) in atlas.Glyphs)
            {
                FontGlyph glyph = atlasGlyph.FontGlyph;

                var advance = 0;
                var bearing = 0;
                StbTrueType.stbtt_GetCodepointHMetrics(stbFont, charIndex, &advance, &bearing);
                Assert.True(advance == glyph.AdvanceWidth);
                Assert.True(bearing == glyph.LeftSideBearing || glyph.LeftSideBearing == 0); // stb has junk data beyond valid

                var minX = 0;
                var maxX = 0;
                var minY = 0;
                var maxY = 0;
                StbTrueType.stbtt_GetCodepointBitmapBoxSubpixel(stbFont, charIndex, atlas.RenderScale, atlas.RenderScale, 0, 0, &minX, &minY, &maxX, &maxY);

                Rectangle bbox = glyph.GetBBox(atlas.RenderScale);

                Assert.Equal(minX, bbox.X);
                Assert.Equal(minY, bbox.Y);
                Assert.Equal(maxX, bbox.Width);
                Assert.Equal(maxY, bbox.Height);

                Rectangle drawBox = new Rectangle(0, 0, bbox.Width - bbox.X, bbox.Height - bbox.Y);
                drawBox.Size += Vector2.One; // Add padding from stb
                StbTrueType.stbrp_rect rect = rects[charIndex];
                Assert.Equal(rect.w, drawBox.Width);
                Assert.Equal(rect.h, drawBox.Height);
            }
        }
 /// <summary>
 /// Render a string from an atlas.
 /// </summary>
 /// <param name="position">The top left position of where to start drawing the string.</param>
 /// <param name="color">The text color.</param>
 /// <param name="text">The text itself.</param>
 /// <param name="atlas">The font atlas to use.</param>
 public void RenderString(Vector3 position, Color color, string text, DrawableFontAtlas atlas)
 {
     if (atlas?.Atlas?.Glyphs == null)
     {
         return;
     }
     RenderString(position, color, text, atlas, new TextLayouter(atlas.Atlas));
 }
예제 #4
0
        public void Load()
        {
            // Load assets
            _ubuntuFontAsset = Engine.AssetLoader.Get <FontAsset>(RESOURCE_NAME_FONT_UBUNTU).GetAtlas(12);
            _tileTexture     = Engine.AssetLoader.Get <TextureAsset>(RESOURCE_NAME_TEXTURE_TILE_OUTLINED_CARDINAL);
            _shader          = Engine.AssetLoader.Get <ShaderAsset>(RESOURCE_NAME_SHADER);

            // tile count is vertexCount-1, because end vertices are included in only 1 tile (per axis).
            tiles = new Quadrilateral[MAP_WIDTH - 1, MAP_HEIGHT - 1];

            // Generate a random-ish height map.
            for (var x = 0; x < MAP_WIDTH; x++)
            {
                for (var y = 0; y < MAP_HEIGHT; y++)
                {
                    // Assign in height map @ appropriate position, storing for later use
                    _heightMap[x, y] = _random.Next((int)TileSize.Z);
                }
            }


            for (var y = 0; y < MAP_HEIGHT - 1; y++)
            {
                for (var x = 0; x < MAP_WIDTH - 1; x++)
                {
                    // Assign in height map @ appropriate position, storing for later use
                    var quad = new Quadrilateral(
                        Vector3.Transform(new Vector3(x * TileSize.X, y * TileSize.Y, _heightMap[x, y]), _rotationMatrix),
                        Vector3.Transform(new Vector3((x + 1) * TileSize.X, y * TileSize.Y, _heightMap[x + 1, y]), _rotationMatrix),
                        Vector3.Transform(new Vector3((x + 1) * TileSize.X, (y + 1) * TileSize.Y, _heightMap[x + 1, y + 1]), _rotationMatrix),
                        Vector3.Transform(new Vector3(x * TileSize.X, (y + 1) * TileSize.Y, _heightMap[x, y + 1]), _rotationMatrix)
                        );
                    tiles[x, y] = quad;
                }
            }

            Rectangle boundsOfMap = Rectangle.BoundsFromPolygonPoints(new[]
            {
                tiles[0, 0].Vertex0.ToVec2(),
                tiles[MAP_WIDTH - 2, 0].Vertex1.ToVec2(),
                tiles[MAP_WIDTH - 2, MAP_HEIGHT - 2].Vertex2.ToVec2(),
                tiles[0, MAP_HEIGHT - 2].Vertex3.ToVec2(),
            });

            quadTree = new QuadTree <Quadrilateral>(boundsOfMap, 100);
            foreach (var tile in tiles)
            {
                quadTree.Add(tile);
            }
        }
 /// <summary>
 /// Render a string from an atlas.
 /// </summary>
 /// <param name="position">The top left position of where to start drawing the string.</param>
 /// <param name="color">The text color.</param>
 /// <param name="text">The text itself.</param>
 /// <param name="atlas">The font atlas to use.</param>
 /// <param name="layouter">The layouter to use.</param>
 public void RenderString(Vector3 position, Color color, string text, DrawableFontAtlas atlas, TextLayouter layouter)
 {
     if (atlas?.Atlas?.Glyphs == null)
     {
         return;
     }
     position = position.RoundClosest();
     foreach (char c in text)
     {
         Vector2 gPos = layouter.AddLetter(c, out AtlasGlyph g);
         if (g == null)
         {
             continue;
         }
         var uv = new Rectangle(g.Location, g.UV);
         RenderSprite(new Vector3(position.X + gPos.X, position.Y + gPos.Y, position.Z), g.Size, color, atlas.Texture, uv);
     }
 }
예제 #6
0
        protected override void RenderContent(RenderComposer composer)
        {
            Vector2 offset = ImGui.GetWindowPos();

            offset.Y   += ImGui.GetWindowHeight();
            _lastOffset = offset;
            composer.RenderSprite(new Vector3(offset, 0), _anim.Texture.Size * _parent.Scale, Color.White, _anim.Texture);
            for (var i = 0; i < _anim.Frames.Length; i++)
            {
                composer.RenderOutline(new Vector3(offset + _anim.Frames[i].Position * _parent.Scale, 1), _anim.Frames[i].Size * _parent.Scale, _holdingIdx == i ? Color.Green : Color.Red);
                var stringPos           = new Vector3(offset + _anim.Frames[i].Position * _parent.Scale, 1);
                DrawableFontAtlas atlas = _font.GetAtlas(15 * _parent.Scale);
                composer.RenderString(stringPos + new Vector3(1), Color.Black, i.ToString(), atlas);
                composer.RenderString(stringPos, Color.Red, i.ToString(), atlas);
            }

            ImGui.Text(_holdingIdx == -1 ? "Click on a rectangle to change it's position." : $"Select new position for frame {_holdingIdx}!");
        }
예제 #7
0
        public override void Draw(RenderComposer composer)
        {
            DrawableFontAtlas atlas = _font.GetAtlas(30);
            var    l = new TextLayouter(atlas.Atlas);
            string text;

            if (!_myPaddle.Ready)
            {
                text = "Press 'Space' when ready!";
            }
            else if (_pad1 == _myPaddle && !_pad2.Ready || _pad2 == _myPaddle && !_pad1.Ready)
            {
                text = "Waiting for other player.";
            }
            else
            {
                text = $"{_pad1.Score}/{_pad2.Score}";
            }

            Vector2 textSize = l.MeasureString(text);
            float   screenHorizontalCenter = Engine.Configuration.RenderSize.X / 2;

            composer.RenderString(new Vector3(screenHorizontalCenter - textSize.X / 2, 10, 0), Color.White, text, atlas);

            composer.RenderSprite(_pad1.VisualPosition, _pad1.Size, _pad1.Ready ? Color.White : Color.Red);
            composer.RenderSprite(_pad2.VisualPosition, _pad2.Size, _pad2.Ready ? Color.White : Color.Red);

            // Uncomment to view paddle collisions.
            //LineSegment[] padCol = _pad2.GetPaddleCollision();
            //for (var i = 0; i < padCol.Length; i++)
            //{
            //    composer.RenderLine(ref padCol[i], Color.Red);
            //}

            NetworkTransform ball = IdToObject["Ball"];

            composer.RenderSprite(ball.VisualPosition, ball.Size, Color.White);

            NetworkTransform upperWall = IdToObject["UpperWall"];
            NetworkTransform lowerWall = IdToObject["LowerWall"];

            composer.RenderSprite(upperWall.VisualPosition, upperWall.Size, Color.White);
            composer.RenderSprite(lowerWall.VisualPosition, lowerWall.Size, Color.White);
        }
예제 #8
0
        public static unsafe DrawableFontAtlas RenderFontStbPacked(byte[] ttf, float fontSize, Vector2 atlasSize, int numChars, Font f, out StbTrueType.stbtt_fontinfo fontInfo)
        {
            var atlasObj = new DrawableFontAtlas(f, fontSize);

            fontSize = atlasObj.FontSize;

            fontInfo = new StbTrueType.stbtt_fontinfo();
            fixed(byte *ttPtr = &ttf[0])
            {
                StbTrueType.stbtt_InitFont(fontInfo, ttPtr, 0);

                float scaleFactor = StbTrueType.stbtt_ScaleForMappingEmToPixels(fontInfo, fontSize);
                int   ascent, descent, lineGap;

                StbTrueType.stbtt_GetFontVMetrics(fontInfo, &ascent, &descent, &lineGap);

                atlasSize *= 3; // Needs to be big as the packing sucks, and glyphs getting cut out messes with the tests.
                var pixels = new byte[(int)atlasSize.X * (int)atlasSize.Y];

                var pc = new StbTrueType.stbtt_pack_context();

                fixed(byte *pixelsPtr = pixels)
                {
                    StbTrueType.stbtt_PackBegin(pc, pixelsPtr, (int)atlasSize.X, (int)atlasSize.Y, (int)atlasSize.X, 1, null);
                }

                var cd = new StbTrueType.stbtt_packedchar[numChars];

                fixed(StbTrueType.stbtt_packedchar *charPtr = cd)
                {
                    StbTrueType.stbtt_PackFontRange(pc, ttPtr, 0, -fontSize, 0, numChars, charPtr);
                }

                StbTrueType.stbtt_PackEnd(pc);
                for (var i = 0; i < cd.Length; ++i)
                {
                    var atlasGlyph = DrawableGlyph.CreateForTest(cd[i].xadvance, cd[i].xoff, cd[i].x1 - cd[i].x0, cd[i].y1 - cd[i].y0);
                    atlasGlyph.GlyphUV       = new Rectangle(cd[i].x0, cd[i].y0, atlasGlyph.Width, atlasGlyph.Height);
                    atlasObj.Glyphs[(char)i] = atlasGlyph;
                }
            }

            return(atlasObj);
        }
예제 #9
0
 public TextLayouter(DrawableFontAtlas atlas)
 {
     SetAtlas(atlas);
 }
예제 #10
0
 /// <summary>
 /// Set a new font atlas.
 /// </summary>
 /// <param name="atlas">The atlas to set.</param>
 public void SetAtlas(DrawableFontAtlas atlas)
 {
     Restart();
     _atlas        = atlas;
     _hasZeroGlyph = atlas.Glyphs.ContainsKey((char)0);
 }
예제 #11
0
        protected override bool RenderInternal(RenderComposer c)
        {
            var open = true;

            ImGui.SetNextWindowPos(new Vector2(0, 20), ImGuiCond.Always);
            ImGui.SetNextWindowSize(c.CurrentTarget.Size - new Vector2(0, 20));
            ImGui.Begin(Title, ref open, ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove);

            RenderImGui();

            ImGui.End();

            Position = Vector3.Zero;
            Size     = c.CurrentTarget.Size;
            if (!open)
            {
                Parent?.RemoveChild(this);
                return(false);
            }

            if (_textureFb?.ColorAttachment == null)
            {
                return(true);                                     // Disposed or uninitialized fb
            }
            if (_currentAssetTexture == null)
            {
                return(true);
            }

            _textureFb.ColorAttachment.Smooth = false;
            c.RenderToAndClear(_textureFb);
            c.RenderSprite(Vector3.Zero, _currentAssetTexture.Size, _currentAssetTexture);

            // Render meta overlay on the spritesheet texture.
            if (_currentAsset != null)
            {
                AnimatedSprite             currentFileContext = _currentAsset.Content !;
                SpriteAnimationFrameSource frameSource        = currentFileContext.FrameSource;
                if (frameSource != null)
                {
                    for (var i = 0; i < frameSource.GetFrameCount(); i++)
                    {
                        Rectangle frameUv = frameSource.GetFrameUV(i);
                        c.RenderOutline(frameUv, _selectedFrame == i ? Color.Green : Color.Red);

                        if (_showFrameIdx && frameSource is SpriteArrayFrameSource)
                        {
                            Vector3           stringPos = frameUv.Position.ToVec3();
                            DrawableFontAtlas atlas     = _debugFont.GetAtlas(20);
                            c.RenderString(stringPos + new Vector3(1), Color.Black, i.ToString(), atlas);
                            c.RenderString(stringPos, Color.Red, i.ToString(), atlas);
                        }
                    }
                }
            }

            c.RenderTo(null);

            RenderAnimationPreview(c);

            return(true);
        }
예제 #12
0
        public void VerifyFontRendering()
        {
            var fonts = new[]
            {
                "Junction-Bold.otf",   // Cff
                "CaslonOS.otf",        // Cff 2 (covers other cases)
                "1980XX.ttf",          // Ttf
                "LatoWeb-Regular.ttf", // Composite
                "Junction-Bold.otf",   // 14 font size
                "Junction-Bold.otf"    // 11 font size
            };

            var names = new[]
            {
                "Junction-Bold",
                "CaslonOS-Regular",
                "1980XX",
                "Lato Regular",
                "Junction-Bold",
                "Junction-Bold"
            };

            var unitsPerEm = new[]
            {
                1000,
                1000,
                1024,
                2000,
                1000,
                1000
            };

            int[] descender =
            {
                -250,
                -360,
                -128,
                -426,
                -250,
                -250
            };

            var ascender = new[]
            {
                750,
                840,
                682,
                1974,
                750,
                750
            };

            var glyphs = new[]
            {
                270,
                279,
                141,
                2164,
                270,
                270
            };

            string[] cachedRender =
            {
                ResultDb.EmotionCffAtlas,
                "",
                ResultDb.EmotionTtAtlas,
                ResultDb.EmotionCompositeAtlas,
                "",
                ""
            };

            int[] fontSizes =
            {
                17,
                17,
                17,
                17,
                14,
                11
            };

            FrameBuffer b = null;

            for (var i = 0; i < fonts.Length; i++)
            {
                Engine.Log.Info($"Running font {fonts[i]} ({i})...", TestRunnerLogger.TestRunnerSrc);
                ReadOnlyMemory <byte> data = Engine.AssetLoader.Get <OtherAsset>($"Fonts/{fonts[i]}")?.Content ?? ReadOnlyMemory <byte> .Empty;
                var f = new Font(data);

                // Verify basic font data.
                Assert.True(f.Valid);
                Assert.True(f.FullName == names[i]);
                Assert.True(f.UnitsPerEm == unitsPerEm[i]);
                Assert.True(f.Descender == descender[i]);
                Assert.True(f.Ascender == ascender[i]);
                Assert.True(f.CharToGlyph.Count == glyphs[i]);

                // Get atlases.
                int fontSize     = fontSizes[i];
                var emotionAtlas = new StbDrawableFontAtlas(f, fontSize, false);
                Runner.ExecuteAsLoop(_ =>
                {
                    var str = "";
                    for (uint j = emotionAtlas.Font.FirstCharIndex; j < emotionAtlas.Font.LastCharIndex; j++)
                    {
                        str += (char)j;
                    }

                    emotionAtlas.CacheGlyphs(str);
                }).WaitOne();
                DrawableFontAtlas packedStbAtlas = RenderFontStbPacked(data.ToArray(), fontSize, emotionAtlas.Texture.Size * 3, (int)f.LastCharIndex + 1, f, out StbTrueType.stbtt_fontinfo stbFont);

                // Compare glyph parsing.
                CompareMetricsWithStb(f, emotionAtlas, stbFont);

                // Compare render metrics.
                foreach (KeyValuePair <char, DrawableGlyph> g in emotionAtlas.Glyphs)
                {
                    DrawableGlyph glyph = packedStbAtlas.Glyphs[g.Key];
                    Assert.Equal(glyph.XAdvance, g.Value.XAdvance);

                    var fontGlyph = g.Value.FontGlyph;
                    int width     = (int)(MathF.Ceiling(fontGlyph.Max.X * emotionAtlas.RenderScale) - MathF.Floor(fontGlyph.Min.X * emotionAtlas.RenderScale));
                    int height    = (int)(MathF.Ceiling(-fontGlyph.Min.Y * emotionAtlas.RenderScale) - MathF.Floor(-fontGlyph.Max.Y * emotionAtlas.RenderScale));

                    Assert.Equal(glyph.Width, width);
                    Assert.Equal(glyph.Height, height);
                }

                // Check if there's a verified render.
                if (string.IsNullOrEmpty(cachedRender[i]))
                {
                    continue;
                }

                // Compare with cached render.
                // ReSharper disable AccessToModifiedClosure

                Runner.ExecuteAsLoop(_ =>
                {
                    if (b == null)
                    {
                        b = new FrameBuffer(emotionAtlas.Texture.Size).WithColor();
                    }
                    else
                    {
                        b.Resize(emotionAtlas.Texture.Size, true);
                    }

                    RenderComposer composer = Engine.Renderer.StartFrame();
                    composer.RenderToAndClear(b);
                    composer.RenderSprite(Vector3.Zero, emotionAtlas.Texture.Size, Color.White, emotionAtlas.Texture);
                    composer.RenderTo(null);
                    Engine.Renderer.EndFrame();
                    Runner.VerifyScreenshot(cachedRender[i], b);
                }).WaitOne();
            }
        }
예제 #13
0
 public TextLayouterWrap(DrawableFontAtlas atlas) : base(atlas)
 {
 }