Table of Contents

3D Letters (Mesh Text)

A gallery of every glyph LetterMeshFactory can build - the digits, the full A-Z alphabet and the dash - as solid extruded meshes that catch the light like any other geometry, plus a frame counter whose digits are rebuilt as a new mesh every frame. The counter demonstrates the one rule of dynamic mesh text: dispose the old GPU buffers before swapping in the new mesh, or leak a buffer pair per rebuild.

The Program.cs file shows how to:

  • Solid 3D lettering from code: LetterMeshFactory.CreateTextMeshDraw
  • Which characters exist - SupportedCharacters - and why fonts are not involved
  • Static lettering built once versus text that changes
  • Rebuilding a mesh safely: dispose the old buffers first
  • centerOrigin for strings centred on their entity
  • When to use EntityTextComponent or WorldTextComponent instead

3D Letters (Mesh Text)

View on GitHub.

using E03_3D_MeshText;
using Stride.CommunityToolkit.Bepu;
using Stride.CommunityToolkit.Engine;
using Stride.CommunityToolkit.Rendering.Utilities;
using Stride.CommunityToolkit.Skyboxes;
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Rendering;

// A gallery of every glyph LetterMeshFactory can build, plus a frame counter whose digits are
// rebuilt as solid geometry every frame.
//
// LetterMeshFactory makes letters that are MESHES: real extruded geometry that catches the light,
// takes a material, casts onto the scene like any other model - and could carry a physics body and
// tumble (E20_3D_CubeCollapse drops its GAME OVER that way). The price is that only the
// characters someone has authored exist; see LetterMeshFactory.SupportedCharacters.
//
// For ordinary text in a real font - labels, HUDs, signs - use E03_3D_EntityText (screen-space)
// or E03_3D_WorldText (in-scene) instead.

using var game = new Game();

game.Run(start: Start);

void Start(Scene scene)
{
    game.SetupBase3DScene();
    game.AddSkybox();
    game.AddProfiler();

    // The base scene's single directional light shines from behind this gallery, leaving the front
    // caps in the dark. Six lights down the six world axes make solid lettering read from any side.
    game.AddAllDirectionLighting(intensity: 20f, showLightGizmo: false);

    // The default camera starts at (6, 6, 6) looking at the origin, so the whole gallery is turned
    // 45 degrees to face it square-on. Fly around it - these are solids, not billboards.
    var faceCamera = Quaternion.RotationY(MathUtil.DegreesToRadians(45));

    var gold = game.CreateMaterial(Color.Gold);
    var silver = game.CreateMaterial(new Color(210, 215, 225));
    var blue = game.CreateMaterial(new Color(120, 190, 255));

    // Every authored glyph, three lines: the digits and the dash, then the full alphabet
    AddLine(scene, "0123456789-", new Vector3(0, 3.9f, 0), gold, faceCamera);
    AddLine(scene, "ABCDEFGHIJKLM", new Vector3(0, 2.7f, 0), silver, faceCamera, scale: 0.5f);
    AddLine(scene, "NOPQRSTUVWXYZ", new Vector3(0, 1.5f, 0), silver, faceCamera, scale: 0.5f);

    // The counter: a script that replaces its digit mesh whenever the number changes
    var counter = new Entity("FrameCounter")
    {
        new FrameCounterScript { Material = blue }
    };

    // A step toward the camera, so it sits in front of the gallery
    counter.Transform.Position = new Vector3(1f, 0.5f, 1f);
    counter.Transform.Rotation = faceCamera;
    counter.Transform.Scale = new Vector3(0.7f);
    counter.Scene = scene;
}

// One line of the gallery: a single mesh containing the whole string, built once and never touched
// again. Static lettering is this cheap - the cost lives only where text changes.
void AddLine(Scene scene, string text, Vector3 position, Material material, Quaternion rotation, float scale = 0.55f)
{
    var entity = new Entity($"Letters {text}")
    {
        new ModelComponent
        {
            Model = new Model
            {
                new MaterialInstance { Material = material },
                new Mesh
                {
                    // centerOrigin centres the string on the entity, so every line can share X = 0
                    Draw = LetterMeshFactory.CreateTextMeshDraw(game.GraphicsDevice, text, centerOrigin: true),
                    MaterialIndex = 0
                }
            }
        }
    };

    entity.Transform.Position = position;
    entity.Transform.Rotation = rotation;
    entity.Transform.Scale = new Vector3(scale);
    entity.Scene = scene;
}