Search Unity

Draw a line from A to B

Discussion in 'UIElements' started by cecarlsen, Jun 21, 2019.

  1. cecarlsen

    cecarlsen

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

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    522
    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. }
     
    Pelican_7 and eidetic like this.
  3. eidetic

    eidetic

    Joined:
    Jun 27, 2016
    Posts:
    12
    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:
    226
    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:
    30
    So its not in any 2019.3 alpha yet?!
     
  6. AlexandreT-unity

    AlexandreT-unity

    Unity Technologies

    Joined:
    Feb 1, 2018
    Posts:
    24
    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
    Circool, mrSaig and Pelican_7 like this.
  7. taylank

    taylank

    Joined:
    Nov 3, 2012
    Posts:
    102
    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:
    24
  9. LForward1

    LForward1

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

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    522
    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:
    102
    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:
    29
    Yup, Vertex.NearZ should be used for all Vertices z values.
     
  13. awesomedata

    awesomedata

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

    taylank

    Joined:
    Nov 3, 2012
    Posts:
    102
    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:
    522
    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:
    101
    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:
    522
    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:
    12
    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:
    102
    Does it help when you call MarkDirtyRepaint on your element?
     
  20. eidetic

    eidetic

    Joined:
    Jun 27, 2016
    Posts:
    12
    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:
    5

    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:
    102
    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.