Search Unity

Draw a line from A to B

Discussion in 'UI Toolkit' started by cecarlsen, Jun 21, 2019.

  1. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    864
    Last edited: Jun 21, 2019
  2. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    864
    It turns out that using the ImmediateModeElement is a very flexible way to draw whatever you like. Here is an example.

    Code (CSharp):
    1. public class ConnectionElement : ImmediateModeElement
    2. {
    3.     Vector2 _positionA;
    4.     Vector2 _positionB;
    5.  
    6.     static Material _lineMaterial;
    7.  
    8.     public Vector2 positionA {
    9.         get { return _positionA; }
    10.         set {
    11.             _positionA = value;
    12.             UpdateBounds();
    13.         }
    14.     }
    15.  
    16.         public Vector2 positionB {
    17.         get { return _positionB; }
    18.         set {
    19.             _positionB = value;
    20.             UpdateBounds();
    21.         }
    22.     }
    23.  
    24.  
    25.     void UpdateBounds()
    26.     {
    27.         Vector2 min = Vector2.Min( _positionA, _positionB );
    28.         Vector2 max = Vector2.Max( _positionA, _positionB );
    29.  
    30.         style.left = min.x;
    31.         style.top = min.y;
    32.         style.width = max.x-min.x;
    33.         style.height = max.y-min.y;
    34.  
    35.         MarkDirtyRepaint();
    36.     }
    37.  
    38.      
    39.     protected override void ImmediateRepaint()
    40.     {
    41.         GetLineMaterial().SetPass( 0 );
    42.  
    43.         Vector2 min = Vector2.Min( _positionA, _positionB );
    44.         Vector3 relA = _positionA - min;
    45.         Vector3 relB = _positionB - min;
    46.  
    47.         GL.Begin( GL.LINES );
    48.         GL.Color( Color.white );
    49.         GL.Vertex3( relA.x, relA.y, 0 );
    50.         GL.Vertex3( relB.x, relB.y, 0 );
    51.         GL.End();
    52.  
    53.     }
    54.  
    55.  
    56.     static Material GetLineMaterial()
    57.     {
    58.         if( !_lineMaterial )
    59.         {
    60.             Shader shader = Shader.Find("Hidden/Internal-Colored");
    61.             _lineMaterial = new Material(shader);
    62.             _lineMaterial.hideFlags = HideFlags.HideAndDontSave;
    63.             _lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
    64.             _lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
    65.             _lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
    66.             _lineMaterial.SetInt("_ZWrite", 0);
    67.         }
    68.  
    69.         return _lineMaterial;
    70.     }
    71. }
     
  3. eidetic

    eidetic

    Joined:
    Jun 27, 2016
    Posts:
    14
    Awesome! Thanks for the update @cecarlsen... time to update my IMGUIContainers...

    One step closer to removing dependency on the UnityEditor APIs.
     
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    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.
     
  5. mrSaig

    mrSaig

    Joined:
    Jan 14, 2010
    Posts:
    68
    So its not in any 2019.3 alpha yet?!
     
  6. AlexandreT-unity

    AlexandreT-unity

    Unity Technologies

    Joined:
    Feb 1, 2018
    Posts:
    377
    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.
     
    Last edited: Jul 18, 2019
  7. taylank

    taylank

    Joined:
    Nov 3, 2012
    Posts:
    182
    Sorry, where would I look for them exactly? The documentation turns up empty when I search for them. What class are they under?
     
  8. AlexandreT-unity

    AlexandreT-unity

    Unity Technologies

    Joined:
    Feb 1, 2018
    Posts:
    377
  9. LForward1

    LForward1

    Joined:
    Jul 22, 2019
    Posts:
    12
    Dude, I thought it is gonna be harder, thank you for information.
     
  10. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    864
    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.

    Code (CSharp):
    1. void OnGenerateVisualContent( MeshGenerationContext cxt )
    2. {
    3.     MeshWriteData mesh = cxt.Allocate( 3, 3 );
    4.     UnityEngine.UIElements.Vertex[] vertices = new UnityEngine.UIElements.Vertex[3];
    5.     vertices[0].position = Vector3.zero;
    6.     vertices[1].position = Vector3.up * 10;
    7.     vertices[2].position = Vector3.right * 10;
    8.     vertices[0].tint = Color.white;
    9.     vertices[1].tint = Color.white;
    10.     vertices[2].tint = Color.white;
    11.     mesh.SetAllVertices( vertices );
    12.     mesh.SetAllIndices( new ushort[]{ 0, 1, 2 }  );
    13. }
    What am I missing for my first UIElements triangle? I'm on Alpha 10.
     
    Last edited: Jul 26, 2019
  11. taylank

    taylank

    Joined:
    Nov 3, 2012
    Posts:
    182
    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?
     
  12. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    Yup, Vertex.NearZ should be used for all Vertices z values.
     
  13. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    Totally needs to be in the documentation -- and possibly in the Intellisense too. D:
     
  14. taylank

    taylank

    Joined:
    Nov 3, 2012
    Posts:
    182
    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?
     
    awesomedata likes this.
  15. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    864
    Ah, the missing piece! Thank you @taylank and @UnityMat.

    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?
     
    Last edited: Jul 29, 2019
  16. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    780
    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.



     
  17. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    864
    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).
     
  18. eidetic

    eidetic

    Joined:
    Jun 27, 2016
    Posts:
    14
    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.

    https://imgur.com/a/AYopwFT

    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:

    Code (CSharp):
    1.  
    2.         public static void DrawCable(Vector3[] points, float thickness, Color color, MeshGenerationContext context)
    3.         {
    4.             List<Vertex> vertices = new List<Vertex>();
    5.             List<ushort> indices = new List<ushort>();
    6.  
    7.             for (int i = 0; i < points.Length - 1; i++)
    8.             {
    9.                 var pointA = points[i];
    10.                 var pointB = points[i + 1];
    11.  
    12.                 float angle = Mathf.Atan2(pointB.y - pointA.y, pointB.x - pointA.x);
    13.                 float offsetX = thickness / 2 * Mathf.Sin(angle);
    14.                 float offsetY = thickness / 2 * Mathf.Cos(angle);
    15.  
    16.                 vertices.Add(new Vertex()
    17.                 {
    18.                     position = new Vector3(pointA.x + offsetX, pointA.y - offsetY, Vertex.nearZ),
    19.                     tint = color
    20.                 });
    21.                 vertices.Add(new Vertex()
    22.                 {
    23.                     position = new Vector3(pointB.x + offsetX, pointB.y - offsetY, Vertex.nearZ),
    24.                     tint = color
    25.                 });
    26.                 vertices.Add(new Vertex()
    27.                 {
    28.                     position = new Vector3(pointB.x - offsetX, pointB.y + offsetY, Vertex.nearZ),
    29.                     tint = color
    30.                 });
    31.                 vertices.Add(new Vertex()
    32.                 {
    33.                     position = new Vector3(pointB.x - offsetX, pointB.y + offsetY, Vertex.nearZ),
    34.                     tint = color
    35.                 });
    36.                 vertices.Add(new Vertex()
    37.                 {
    38.                     position = new Vector3(pointA.x - offsetX, pointA.y + offsetY, Vertex.nearZ),
    39.                     tint = color
    40.                 });
    41.                 vertices.Add(new Vertex()
    42.                 {
    43.                     position = new Vector3(pointA.x + offsetX, pointA.y - offsetY, Vertex.nearZ),
    44.                     tint = color
    45.                 });
    46.  
    47.                 ushort indexOffset(int value) => (ushort)(value + (i * 6));
    48.                 indices.Add(indexOffset(0));
    49.                 indices.Add(indexOffset(1));
    50.                 indices.Add(indexOffset(2));
    51.                 indices.Add(indexOffset(3));
    52.                 indices.Add(indexOffset(4));
    53.                 indices.Add(indexOffset(5));
    54.             }
    55.  
    56.             var mesh = context.Allocate(vertices.Count, indices.Count);
    57.             mesh.SetAllVertices(vertices.ToArray());
    58.             mesh.SetAllIndices(indices.ToArray());
    59.         }

    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.
     
  19. taylank

    taylank

    Joined:
    Nov 3, 2012
    Posts:
    182
    Does it help when you call MarkDirtyRepaint on your element?
     
  20. eidetic

    eidetic

    Joined:
    Jun 27, 2016
    Posts:
    14
    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.
     
  21. MomoRazor

    MomoRazor

    Joined:
    Apr 30, 2016
    Posts:
    6

    Hi there, a bit late to the game, but I was wondering if I could get an example of how I can use this class to draw arrows between 2 VisualElements. It seems to work using positions, but I'm unsure of how I can get the positions of VisualElements. I'm rather new to the whole UIElements thing. Thanks for the help!
     
  22. taylank

    taylank

    Joined:
    Nov 3, 2012
    Posts:
    182
    You can use VisualElement.transform.position, although this will give you the coordinates of the top left corner of the element bounds. To draw from the center you can use visualElement.worldBound.center.
     
    cecarlsen likes this.
  23. rayming_smt

    rayming_smt

    Joined:
    Oct 3, 2019
    Posts:
    29
    Hi, I'm able to reproduce this particular problem. It happens when the triangle is defined in clock-wise.
    For example, when this mesh is created this way:
    Code (CSharp):
    1. var mwd = context.Allocate(3, 3);
    2. mwd.SetNextVertex(new Vertex()
    3. {
    4.     position = new Vector3(-6.5f, -13.5f, -1.0f),
    5.     tint = Color.white
    6. });
    7.  
    8.  
    9. mwd.SetNextVertex(new Vertex()
    10. {
    11.     position = new Vector3(103.5f, 96.5f, -1.0f),
    12.     tint = Color.green
    13. });
    14.  
    15.  
    16. mwd.SetNextVertex(new Vertex()
    17. {
    18.     position = new Vector3(96.5f, 103.5f, -1.0f),
    19.     tint = Color.blue
    20. });
    21. mwd.SetNextIndex(0);
    22. mwd.SetNextIndex(1);
    23. mwd.SetNextIndex(2);
    The mesh will not render until I add the class 'unity-button' to the VisualElement.

    However, if I swap the indices sequence like this:
    Code (CSharp):
    1. mwd.SetNextIndex(0);
    2. mwd.SetNextIndex(2);
    3. mwd.SetNextIndex(1);
    It starts working normally again.
     
  24. AlexandreT-unity

    AlexandreT-unity

    Unity Technologies

    Joined:
    Feb 1, 2018
    Posts:
    377
    Hi rayming_smt,

    It is a bug, please submit a case asap through the bug reporter (see "Help/Report a bug..."). Hopefully we can fix this before the release of 19.3.

    Also, please make sure you set the Z of your vertices to Vertex.nearZ as stated in the documentation. Do NOT hard-code any value for Z. For instance, there is a bugfix on the way that changes the value of nearZ. In the future we may change nearZ to zero, but we're not there yet.

    Thanks
     
    Last edited: Oct 18, 2019
  25. wbahnassi_unity

    wbahnassi_unity

    Unity Technologies

    Joined:
    Mar 12, 2018
    Posts:
    28
    Yes please! And thanks a lot for being patient with this new API. We will add sample code around it so people can have a good jump start instead of struggling with an empty screen for a long time :)
     
  26. rayming_smt

    rayming_smt

    Joined:
    Oct 3, 2019
    Posts:
    29
    I just submitted the report. God speed, I'm really excited about this feature.

    Thanks for the tip.
     
    AlexandreT-unity likes this.
  27. AlexandreT-unity

    AlexandreT-unity

    Unity Technologies

    Joined:
    Feb 1, 2018
    Posts:
    377
    rayming_smt likes this.
  28. AlexandreT-unity

    AlexandreT-unity

    Unity Technologies

    Joined:
    Feb 1, 2018
    Posts:
    377
    @rayming_smt The issue is virtually fixed and should be part of the next releases of 19.3/20.1. Thanks again!

    To anyone using the mesh API, please note that it will be necessary to swap your indices to ensure that you use clockwise winding order.
     
  29. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    864
    What is best practice for anti aliasing shapes drawn using MeshGenerationContext? My lines are looking itchy.
     
  30. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    1,003
    You have basically only one option at this time: you can enable MSAA antialiasing on your EditorWindow. This is done through an extension method:

    Code (CSharp):
    1. public static void SetAntiAliasing(this EditorWindow window, int aa);
    The
    aa
    value is the number of samples per pixels (1 by default). You can add the following line in your EditorWindow's OnEnable() method to have 4 samples per pixel instead:

    Code (CSharp):
    1. this.SetAntiAliasing(4)
    We are currently evaluating alternative ways to provide antialiased primitives in UIElements.
     
    CyrilGhys likes this.
  31. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    864
    Beautiful! Pleas keep it as simple as this =)