4: Drawing Text in a 3D Scene

This entry is part 4 of a 12-part series on WPF 3D.

Drawing Text in a 3D Scene

This piece of oak is 4 inches wide, 8 inches long, and 1 inch thick.  But that's obvious from the picture, right?


As I was learning WPF, it took me a long time to add annotations to Sawdust.  I procrastinated because I assumed it would be difficult to get text into my 3D scene.  Silly me.

In WPF 3D, drawing text is remarkably simple.  Basically you just make a VisualBrush from a TextBlock.  Use that Brush in a Material and set the TextureCoordinates properly.

Today I'll let the code speak for itself.

 

/// <summary>
///
Creates a ModelVisual3D containing a text label.
/// </summary>
///
<param name="text">The string</param>
///
<param name="textColor">The color of the text.</param>
///
<param name="bDoubleSided">Visible from both sides?</param>
///
<param name="height">Height of the characters</param>
///
<param name="center">The center of the label</param>
///
<param name="over">Horizontal direction of the label</param>
///
<param name="up">Vertical direction of the label</param>
///
<returns>Suitable for adding to your Viewport3D</returns>
public static ModelVisual3D CreateTextLabel3D(
    string text,
    Brush textColor,
    bool bDoubleSided,
    double height,
    Point3D center,
    Vector3D over,
    Vector3D up)
{
    // First we need a textblock containing the text of our label
    TextBlock tb = new TextBlock(new Run(text));
    tb.Foreground = textColor;
    tb.FontFamily = new FontFamily("Arial");

    // Now use that TextBlock as the brush for a material
    DiffuseMaterial mat = new DiffuseMaterial();
    mat.Brush = new VisualBrush(tb);

    // We just assume the characters are square
    double width = text.Length * height;

    // Since the parameter coming in was the center of the label,
    // we need to find the four corners
    // p0 is the lower left corner
    // p1 is the upper left
    // p2 is the lower right
    // p3 is the upper right
    Point3D p0 = center - width / 2 * over - height / 2 * up;
    Point3D p1 = p0 + up * 1 * height;
    Point3D p2 = p0 + over * width;
    Point3D p3 = p0 + up * 1 * height + over * width;

    // Now build the geometry for the sign.  It's just a
    // rectangle made of two triangles, on each side.

    MeshGeometry3D mg = new MeshGeometry3D();
    mg.Positions = new Point3DCollection();
    mg.Positions.Add(p0);    // 0
    mg.Positions.Add(p1);    // 1
    mg.Positions.Add(p2);    // 2
    mg.Positions.Add(p3);    // 3

    if (bDoubleSided)
    {
        mg.Positions.Add(p0);    // 4
        mg.Positions.Add(p1);    // 5
        mg.Positions.Add(p2);    // 6
        mg.Positions.Add(p3);    // 7
    }

    mg.TriangleIndices.Add(0);
    mg.TriangleIndices.Add(3);
    mg.TriangleIndices.Add(1);
    mg.TriangleIndices.Add(0);
    mg.TriangleIndices.Add(2);
    mg.TriangleIndices.Add(3);

    if (bDoubleSided)
    {
        mg.TriangleIndices.Add(4);
        mg.TriangleIndices.Add(5);
        mg.TriangleIndices.Add(7);
        mg.TriangleIndices.Add(4);
        mg.TriangleIndices.Add(7);
        mg.TriangleIndices.Add(6);
    }

    // These texture coordinates basically stretch the
    // TextBox brush to cover the full side of the label.

    mg.TextureCoordinates.Add(new Point(0, 1));
    mg.TextureCoordinates.Add(new Point(0, 0));
    mg.TextureCoordinates.Add(new Point(1, 1));
    mg.TextureCoordinates.Add(new Point(1, 0));

    if (bDoubleSided)
    {
        mg.TextureCoordinates.Add(new Point(1, 1));
        mg.TextureCoordinates.Add(new Point(1, 0));
        mg.TextureCoordinates.Add(new Point(0, 1));
        mg.TextureCoordinates.Add(new Point(0, 0));
    }

    // And that's all.  Return the result.

    ModelVisual3D mv3d = new ModelVisual3D();
    mv3d.Content = new GeometryModel3D(mg, mat);;
    return mv3d;
}