Draw a line from A to B

Hi UIElement specialists

What is the simplest way to drawing a line from position A to position B using UIElements?

It seems people are resorting to IMGUI for this.

EDIT: I see a similar question was asked here:

However, that was half a year ago. Are there any news on this?

~ce

It turns out that using the ImmediateModeElement is a very flexible way to draw whatever you like. Here is an example.

public class ConnectionElement : ImmediateModeElement
{
    Vector2 _positionA;
    Vector2 _positionB;

    static Material _lineMaterial;

    public Vector2 positionA {
        get { return _positionA; }
        set {
            _positionA = value;
            UpdateBounds();
        }
    }

        public Vector2 positionB {
        get { return _positionB; }
        set {
            _positionB = value;
            UpdateBounds();
        }
    }


    void UpdateBounds()
    {
        Vector2 min = Vector2.Min( _positionA, _positionB );
        Vector2 max = Vector2.Max( _positionA, _positionB );

        style.left = min.x;
        style.top = min.y;
        style.width = max.x-min.x;
        style.height = max.y-min.y;

        MarkDirtyRepaint();
    }

      
    protected override void ImmediateRepaint()
    {
        GetLineMaterial().SetPass( 0 );

        Vector2 min = Vector2.Min( _positionA, _positionB );
        Vector3 relA = _positionA - min;
        Vector3 relB = _positionB - min;

        GL.Begin( GL.LINES );
        GL.Color( Color.white );
        GL.Vertex3( relA.x, relA.y, 0 );
        GL.Vertex3( relB.x, relB.y, 0 );
        GL.End();

    }


    static Material GetLineMaterial()
    {
        if( !_lineMaterial )
        {
            Shader shader = Shader.Find("Hidden/Internal-Colored");
            _lineMaterial = new Material(shader);
            _lineMaterial.hideFlags = HideFlags.HideAndDontSave;
            _lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
            _lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
            _lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
            _lineMaterial.SetInt("_ZWrite", 0);
        }

        return _lineMaterial;
    }
}
4 Likes

Awesome! Thanks for the update @cecarlsen ā€¦ time to update my IMGUIContainersā€¦

One step closer to removing dependency on the UnityEditor APIs.

To confirm, ImmediateModeElement is the way to do this in 2019.1/.2 but starting with 2019.3 there will be a new mesh API for UIElements that lets you properly insert your own meshes in the render queue. More details will come closer to 2019.3 release.

4 Likes

So its not in any 2019.3 alpha yet?!

The mesh API landed in 2019.3.0a2. Look for DrawInterface, MeshWriteData and DrawContent.

Edit: The classes/methods are actually VisualElement.generateVisualContent, MeshGenerationContext and MeshWriteData.

4 Likes

Sorry, where would I look for them exactly? The documentation turns up empty when I search for them. What class are they under?

Sorry I gave you class names that have actually been refactored (except MeshWriteData which is still there). Have a look at these documentation entries:

1 Like

Dude, I thought it is gonna be harder, thank you for information.

I canā€™t get anything drawn in the window for some reason. Iā€™m also not sure what coordinate space I am drawing in. I extent the Visual Element and add this method to the geneateVisualContent Action.

void OnGenerateVisualContent( MeshGenerationContext cxt )
{
    MeshWriteData mesh = cxt.Allocate( 3, 3 );
    UnityEngine.UIElements.Vertex[] vertices = new UnityEngine.UIElements.Vertex[3];
    vertices[0].position = Vector3.zero;
    vertices[1].position = Vector3.up * 10;
    vertices[2].position = Vector3.right * 10;
    vertices[0].tint = Color.white;
    vertices[1].tint = Color.white;
    vertices[2].tint = Color.white;
    mesh.SetAllVertices( vertices );
    mesh.SetAllIndices( new ushort[]{ 0, 1, 2 }  );
}

What am I missing for my first UIElements triangle? Iā€™m on Alpha 10.

Could it be a z depth issue? I noticed in the examples provided the z coordinate was set to Vertex.nearZ. Try adding

  • Vector3.forward * Vertex.nearZ to your vertices maybe?

Yup, Vertex.NearZ should be used for all Vertices z values.

Totally needs to be in the documentation ā€“ and possibly in the Intellisense too. D:

What is the relationship between z depth and element sorting? When a visual element is sent to back or brought to front, is it the z value determining the sort order?

1 Like

Ah, the missing piece! Thank you @taylank1 and @uMathieu .

EDIT:

I wish the USS property ā€˜colorā€™ would also apply to the content generated by generateVisualContent. ā€˜background-colorā€™ colors the bounds. Is there a workaround for that or is the only option to pass colors to vertex.tint?

At the moment depth is strictly controlled by the order in the visual element tree. This property will become meaningful once UIElements support 3D rendering but at the moment it should always be set to Vertex.nearZ.

Itā€™s not done automatically by design because even though an element may have additional visual contents it is still able to render a background. Since ā€œcolorā€ only applies to text it doesnā€™t seem appropriate to use it here.

But since the MeshGenerationContext contains the visual element reference, you can use visualElement.resolvedStyle.backgroundColor if youā€™d like to use the same property as well.

Right, I agree. But in my case Iā€™m not drawing text in the element so I will go with this using resolvedStyle. Itā€™s a prettier workaround than reading the colors from C# (like in my related post ).

Hey guys, Iā€™m moving over to using the MeshGenerationContext and having a really strange issueā€¦
The mesh will only display if I add the class ā€˜unity-buttonā€™ to the VisualElement!

In the following video Iā€™m drawing the ā€˜cablesā€™ using MeshGenerationContext, but as you can see, they only appear when I add this ā€˜unity-buttonā€™ class.

I must be something Iā€™m doing in my code because I can get the example from cecarlson to work.

Iā€™m calling the following method from a foreach loop that iterates over each cable:

        public static void DrawCable(Vector3[] points, float thickness, Color color, MeshGenerationContext context)
        {
            List<Vertex> vertices = new List<Vertex>();
            List<ushort> indices = new List<ushort>();

            for (int i = 0; i < points.Length - 1; i++)
            {
                var pointA = points[i];
                var pointB = points[i + 1];

                float angle = Mathf.Atan2(pointB.y - pointA.y, pointB.x - pointA.x);
                float offsetX = thickness / 2 * Mathf.Sin(angle);
                float offsetY = thickness / 2 * Mathf.Cos(angle);

                vertices.Add(new Vertex()
                {
                    position = new Vector3(pointA.x + offsetX, pointA.y - offsetY, Vertex.nearZ),
                    tint = color
                });
                vertices.Add(new Vertex()
                {
                    position = new Vector3(pointB.x + offsetX, pointB.y - offsetY, Vertex.nearZ),
                    tint = color
                });
                vertices.Add(new Vertex()
                {
                    position = new Vector3(pointB.x - offsetX, pointB.y + offsetY, Vertex.nearZ),
                    tint = color
                });
                vertices.Add(new Vertex()
                {
                    position = new Vector3(pointB.x - offsetX, pointB.y + offsetY, Vertex.nearZ),
                    tint = color
                });
                vertices.Add(new Vertex()
                {
                    position = new Vector3(pointA.x - offsetX, pointA.y + offsetY, Vertex.nearZ),
                    tint = color
                });
                vertices.Add(new Vertex()
                {
                    position = new Vector3(pointA.x + offsetX, pointA.y - offsetY, Vertex.nearZ),
                    tint = color
                });

                ushort indexOffset(int value) => (ushort)(value + (i * 6));
                indices.Add(indexOffset(0));
                indices.Add(indexOffset(1));
                indices.Add(indexOffset(2));
                indices.Add(indexOffset(3));
                indices.Add(indexOffset(4));
                indices.Add(indexOffset(5));
            }

            var mesh = context.Allocate(vertices.Count, indices.Count);
            mesh.SetAllVertices(vertices.ToArray());
            mesh.SetAllIndices(indices.ToArray());
        }

This worked just fine when I was calling GL.Vertex3 in a GL.TRIANGLES context (without worrying about indices however).

Iā€™m on 2019.3.0a10.

Does it help when you call MarkDirtyRepaint on your element?

I was calling MarkDirtyRepaint without any success.

Although itā€™s working just fine nowā€¦ and I havenā€™t made any changes in my code since this morning. That makes me think it was a bug in the editor. But Iā€™m not sure what was causing it. If I see this again Iā€™ll try and figure out how to reproduce it and file it as a bug.