0
\$\begingroup\$

Trying to find a way to do as the title says. I'd accept solutions with or without perspective, meaning the line may remain the same thickness regardless of how close or far it gets from the camera, or not (but having both options would be ideal). Here's some of the methods I've found so far, along with reasons why they are not good enough:

LineList / LineStrip Primitive

As described here.

  • ✔ No perspective
  • ✔ Any color
  • ✔ Likely fast
  • ✖ Thickness of 1 only

Projected 2D Drawing

Monogame.Extended includes methods to draw lines (or arbitrary polygons) in 2D space via SpriteBatch. By multiplying the positions with a camera projection before drawing, one can simulate 3D drawing. Here's an example:

// Defines edges of a simple flat arrow centered in (0, 0, 0)
var arrow = new Vector3[]
{
    new Vector3(-.5f, 0, 0),
    new Vector3(0, .5f, 0),
    new Vector3(.5f, 0, 0),
    new Vector3(1/6f, 0, 0),
    new Vector3(1/6f, -.5f, 0),
    new Vector3(-1/6f, -.5f, 0),
    new Vector3(-1/6f, 0, 0),
    new Vector3(-.5f, 0, 0),
};

// Scale, rotation, translation
var trs =
    Matrix.CreateScale(Vector3.One) *
    Matrix.CreateFromYawPitchRoll(0, 0, 0) *
    Matrix.CreateTranslation(position);

// Transform and project to screen
var projected = new Vector3[arrow.Length];
var matrix = trs * cameraViewProjection;
Vector3.Transform(arrow, ref matrix, projected);

// Flatten to 2D, depending on screen size.
// A more complete solution would deal with Z < 0
// differently (points that are behind the camera).
var flattened = projected.Select(v => new Vector2(
        (v.X / v.Z + 1) / 2 * screen.Width,
        (1 - v.Y / v.Z) / 2 * screen.Height)).ToList();

spriteBatch.DrawPolygon(Vector2.Zero, flattened, Color.White, 2);
  • ✔ No perspective
  • ✔ Any color
  • ✔ Any thickness
  • ✖ Probably slow
  • ✖ Z sorting is an issue, it can be remedied but not completely
  • ✖ Frustum culling can also not be fully done

TriangleStrip Primitive

A simple line of arbitrary thickness can be drawn in 3D with just two triangles, or a TriangleStrip with 4 positions. This is what I could achieve so far:

public static class Line
{
    // Two triangles that form a line from (0, 0, 0) to (1, 0, 0), with a thickness of 1
    private static readonly VertexPosition[] Triangles = new VertexPosition[]
    {
        new VertexPosition(new Vector3(0, -.5f, 0)),
        new VertexPosition(new Vector3(0, .5f, 0)),
        new VertexPosition(new Vector3(1, -.5f, 0)),
        new VertexPosition(new Vector3(1, .5f, 0)),
    };

    // Adapted from: https://gist.github.com/kevinmoran/b45980723e53edeb8a5a43c49f134724
    // Return the matrix that would rotate v1 to align to v2.
    public static Matrix RotateAlign(Vector3 v1, Vector3 v2)
    {
        var axis = Vector3.Cross(v1, v2);
        var cosA = Vector3.Dot(v1, v2);
        var k = 1f / (1 + cosA);

        return new Matrix(
            (axis.X * axis.X * k) + cosA, (axis.X * axis.Y * k) + axis.Z, (axis.X * axis.Z * k) - axis.Y, 0,
            (axis.Y * axis.X * k) - axis.Z, (axis.Y * axis.Y * k) + cosA, (axis.Y * axis.Z * k) + axis.X, 0,
            (axis.Z * axis.X * k) + axis.Y, (axis.Z * axis.Y * k) - axis.X, (axis.Z * axis.Z * k) + cosA, 0,
            0, 0, 0, 1);
    }

    public static void Draw(GraphicsDevice graphics, BasicEffect effect, Vector3 a, Vector3 b, float thickness)
    {
        var length = Vector3.Distance(a, b);
        var normalized = (b - a) / length;

        // 1. Scale triangles to the line's length, and the line's thickness.
        // 2. Rotate the triangles to match the line's rotation.
        // 3. Translate the triangles to the line's position.
        effect.World =
            Matrix.CreateScale(new Vector3(length, thickness, 1)) *
            RotateAlign(new Vector3(1, 0, 0), normalized) *
            Matrix.CreateTranslation(a);

        effect.CurrentTechnique.Passes[0].Apply();
        graphics.DrawUserPrimitives(PrimitiveType.TriangleStrip, Triangles, 0, 2);
    }
}
  • ✔ Has perspective
  • ✔ Any color (though not demonstrated in the example)
  • ✔ Any thickness
  • ✔ Likely fast
  • ✖ It's really a flat, stretched plane, which doesn't look right from all angles

Now if it was possible to draw the TriangleStrip as a billboard that always faces the camera, this would be a pretty good solution. But I've tried multiple matrix combinations using Matrix.CreateConstrainedBillboard and could never get it to work properly.

Any help would be appreciated.

\$\endgroup\$

0

You must log in to answer this question.

Browse other questions tagged .