Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Official Introducing the Vector API in Unity 2022.1

Discussion in 'UI Toolkit' started by SimonDufour, Dec 10, 2021.

  1. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    567
    HI SavedByZero, It seems like you are using the Vector graphic package, and it is a similar but different API than what is discussed here. We actually intend on merging both in the future.

    I am not 100% familiar with the one you are using, but I think that all the lines are added to the scene, so you would need to also clear the scene (or make a new one) at each Begin().
     
  2. SavedByZero

    SavedByZero

    Joined:
    May 23, 2013
    Posts:
    124
    Ah, my bad. Looking over the older posts here, this library actually looks closer to what I'm aiming for. Thanks.
     
  3. valiantjohn123

    valiantjohn123

    Joined:
    May 2, 2016
    Posts:
    4
  4. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    567
  5. SavedByZero

    SavedByZero

    Joined:
    May 23, 2013
    Posts:
    124
    How good would this library be for simulating a cell (like inside a human body), with all of it's wiggling and pseudopoding? I've managed to do it very crudely with the previous Vector graphic package, and it kind of works, but doesn't look very good; the gradients don't come out well in the strokes and don't maintain their positions (making it very hard to do strokes with multiple colors without eating the processor) and the curves don't keep their smoothness when it moves.
     
  6. Jonathan-Westfall-8Bits

    Jonathan-Westfall-8Bits

    Joined:
    Sep 17, 2013
    Posts:
    252
    Out of curiosity is there a way to add fill gradients?

    I saw you can make a gradient for strokes, but haven't found a way to add a gradient to the fill.
     
  7. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    396
    Fill gradients are unfortunately not supported
     
  8. Marion

    Marion

    Joined:
    Dec 9, 2012
    Posts:
    2
    Hi, I'm trying to make a radial progress bar. I'm drawing a partial circle, and then a smaller identical circle that is supposed to act as a hole. This appears to work, but I am getting artefacts at certain angles. I haven't really worked with Vector graphics much, could you tell me what I am doing wrong?

    Code:
    paint2D.BeginPath();

    // large wedge
    paint2D.Arc(center, radius , -90, angle - 90f);
    paint2D.LineTo(center);
    paint2D.LineTo(new Vector2(top.x, top.y ));
    paint2D.ClosePath();

    //small wedge
    paint2D.Arc(center, radius - radiusOffset, -90, angle - 90f);
    paint2D.LineTo(center);
    paint2D.LineTo(new Vector2(top.x, top.y));

    paint2D.Fill(FillRule.OddEven);

    Result (at angle 90)
    upload_2022-6-15_6-37-51.png

    at some angles this works fine, e.g. 44.2:
    upload_2022-6-15_6-40-40.png
     
  9. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    This definitely looks like a bug. We'll have a look. In the meantime, if you could open a new case (Help > Report a Bug...) that would be helpful. Thanks!

    As a workaround, you could do a single Arc() call with a Stroke().
     
    awesomedata likes this.
  10. MechaWolf99

    MechaWolf99

    Joined:
    Aug 22, 2017
    Posts:
    293
    Hey, the API looks great. I was wanting to use it for GraphView style lines/connections but that requires gradients. After reading the thread, I take it that these are still not supported and most likely be any time 'soon'?
    Is there a way to manually add it in my self for this specific use? Or is my only option to manually generate the mesh my self like the GraphView does?
     
  11. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    Gradients for strokes will be available in Unity 2022.2, this will allow you to set a Gradient object for the stroke instead of a plain color.

    Otherwise, in 2022.1 you'll have to generate your own connections using the Mesh API.
     
  12. MechaWolf99

    MechaWolf99

    Joined:
    Aug 22, 2017
    Posts:
    293
    Great, thank you! :D
     
  13. Alex-Chouls

    Alex-Chouls

    Joined:
    Mar 24, 2009
    Posts:
    2,652
    Great job on this API, works great!

    Is there any way to hit test against the generated mesh for picking?
    I'm trying to replace my old line drawing library with this API, but so far I have to keep a lot of the old code just for picking, which is a shame. I think it will be a fairly common use case to hit test the generated mesh.
     
    mariandev likes this.
  14. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    Not at this time. One thing to remember is that the mesh generated for the vector primitives will not be exactly what you see on screen. The meshes are larger than what's visible on screen to accommodate the antialiasing and to reduce the polygon count. So using the mesh for hit-testing would be a rough approximation at best.

    So your best bet is probably to override VisualElement.ContainsPoint and to test if the point is close to the geometry you would generate.
     
  15. Blitzkreig95

    Blitzkreig95

    Joined:
    Jul 21, 2021
    Posts:
    20
    @SimonDufour I am a AI and Gameplay Programmer, who is looking into UI Toolkit over the last few weeks. I was wondering if "Painter2D" could take stylus input to draw onto a VisualElement. Based, on my cursory understanding there shouldn't be any reason why it cannot do so. If "pressure" and "position" data of PointerEvent is used to control the MeshGenerationContext, it should be able to do so. I would like your feedback if this makes sense as I am new to the UI side of Unity.
    Also, for DataPersistence, would you suggest capturing the "pressure","position" info and recreating the MeshContext OR just applying the changes to the Mesh - which is more resource efficient in your opinion.

    Lastly, how is "Painter2D" different from SVG. In the thread you happened to mention SVG Importer. Why would you need that now that we have Vector graphics support ?

    Thanks
     
    Last edited: Nov 23, 2022
  16. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    567
    The SVG import .svg files, the Painter2D is a C# used to convert data to some vertex representation used by UI Toolkit. Both are not at the same stage of the pipeline and that is why the SVG importer will most probably used the Painter2d interface internally on the long term.

    Regarding what you are trying to achieve, I would personally:
    • Create a custom stroke renderer c# visual element, that can be fed with a (position+pressure) list and render itself accordingly.
    • Create a "canvas manager" visual element that receive the mouse/pointers events and act accordingly
      • when a stroke begins, Capture the mouse, create a new strokeRenderer and keep a reference to it as an "active stroke"
      • at each event, add the new data to the list and update the active stroke
      • This canvas manager will probably have to support the "undo/delete last stroke" for example
      • Make the link with a scriptable object (save a list of stroke, aka list of list of (point + pressure) )

    On the long term, you will possibly wan to optimize the rendering in the stroke renderer (filter points and remove the one that are really closes together compared to the stroke radius, possibly interpolate to some Bézier curve to get a smoother curve/rendering.

    The mesh itself is not meant to be stored as it is not versioned right now: it could be incompatible with future binary implementation of the shader, and it will be hard to debug. We want to offer such capabilities, but I don't think the feature has landed yet.
     
  17. Blitzkreig95

    Blitzkreig95

    Joined:
    Jul 21, 2021
    Posts:
    20
    So, with regard to "Data Persistence", I will need to store the list of strokes and load it into the VisualElement, for every new lifecycle. That should work until mesh gets binary support in future right ?
     
  18. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    567
  19. Blitzkreig95

    Blitzkreig95

    Joined:
    Jul 21, 2021
    Posts:
    20
    @SimonDufour I am experiencing the following error - A VisualElement must not allocate more than 65535 vertices ,when trying to use painter2D to build a Drawing App.

    I am guessing that there is an upper limit on how many vertices we can have on a VisualElement at a given time. I am already fairly poor in terms of precision of my LineTo() strokes - 30 pixels per second are recorded in my PointerMoveEvent. What is the workaround to get high precision, while managing the constraints imposed by the "MeshGenerationContext" ?

    I have attached the generateVisualContent event handler. Let me know if you need more context. Thanks.

    Code (CSharp):
    1. private void DrawCanvas(MeshGenerationContext ctx)
    2.     {
    3.         var painter = ctx.painter2D;
    4.         painter.lineCap = LineCap.Round;
    5.         painter.lineJoin = LineJoin.Round;
    6.  
    7.         foreach (var lineStroke in DrawingPadStore.LineStrokes)
    8.         {
    9.          
    10.             painter.lineWidth = lineStroke.lineWidth;
    11.             painter.strokeColor = lineStroke.strokeColor;
    12.             painter.BeginPath();
    13.             painter.MoveTo(lineStroke.moveTo);
    14.             foreach (var point in lineStroke.lineTo)
    15.             {
    16.                painter.LineTo(point);
    17.                painter.Stroke();
    18.             }
    19.          
    20.          
    21.         }
     

    Attached Files:

  20. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    567
    Yes, there is a limit. We are aiming to change this in the future, but for now you will have to do multiples lines in multiples element.
     
  21. Arlorean

    Arlorean

    Joined:
    Sep 7, 2014
    Posts:
    27
    Is it possible to have a mode where the gradient stroke is across the width of the stroke instead of along the length?
    This would allow for soft shadow effects for any shape outline, without extra primitives.

    I'm looking for something like this (although for any shaped polyline, not just a circle):
    upload_2022-12-21_17-29-19.png

    Here was my attempt using the new Vector API:

    upload_2022-12-21_17-30-46.png

    Here is the code for this to test:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3.  
    4. namespace UIToolkitTest {
    5.     public class ShapeElement : VisualElement {
    6.         public new class UxmlFactory : UxmlFactory<ShapeElement> { }
    7.  
    8.         public float radius => 50f;
    9.         public float lineWidth => 10f;
    10.         public Gradient lineGradient => new Gradient {
    11.             colorKeys = new GradientColorKey[] {
    12.                 new GradientColorKey(Color.grey, 0),
    13.                 new GradientColorKey(Color.white, 0.5f),
    14.             },
    15.             alphaKeys = new GradientAlphaKey[] {
    16.                 new GradientAlphaKey(0, 0),
    17.                 new GradientAlphaKey(1, 0.5f),
    18.             }
    19.         };
    20.  
    21.         public ShapeElement() {
    22.             style.height = radius * 2;
    23.             style.width = radius * 2;
    24.             generateVisualContent += GenerateVisualContent;
    25.         }
    26.  
    27.         void GenerateVisualContent(MeshGenerationContext ctx) {
    28.             var painter = ctx.painter2D;
    29.             painter.lineWidth = lineWidth;
    30.             painter.strokeGradient = lineGradient;
    31.  
    32.             painter.BeginPath();
    33.             painter.Arc(new(radius, radius), radius-lineWidth/2, 0, 360);
    34.             painter.Stroke();
    35.         }
    36.     }
    37. }
     
    PassivePicasso likes this.
  22. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    567
    Not yet, and it is not planned right not but we do want to add more gradient feature in the future.
     
    Arlorean likes this.
  23. bartuq

    bartuq

    Joined:
    Sep 26, 2016
    Posts:
    127
    Is it possible to make a shape in this way? What I have so far is in the left corner.
     

    Attached Files:

  24. noirb

    noirb

    Joined:
    Apr 10, 2014
    Posts:
    84
    Is there any way to measure how many vertices are being generated as a result of
    Painter2D
    method calls? (e.g. to dynamically generate an additional VisualElement to split a drawing into if it won't fit within the vertex limit)

    Or is there perhaps a minimum number of vertices which will always be generated for every call to
    Painter2D.LineTo()
    ? (I assume it varies due to a number of factors, but just having an approximate lower bound on a "safe" number of calls could be enough to work with)
     
    Last edited: Jan 1, 2023
  25. noirb

    noirb

    Joined:
    Apr 10, 2014
    Posts:
    84
    Also, are there plans to reduce the overhead of repeatedly drawing the same shape? For example, when rendering a lot of identical glyphs like the square markers here, each one may only be 4 verts but they can add up quite quickly and updating them is significantly more expensive than updating the lines that connect them (which are each constructed via
    LineTo
    for each square you see):
    upload_2023-1-1_21-25-13.png

    In many other vector drawing APIs it's often possible to draw a glyph like this just once and then specify locations where it should be repeated, which can often lead to significant memory and CPU savings. (Alternatively, for rendering something like this in UIToolkit--with real-time updates to the graph--is there another method that would be recommended over the Vector API?)
     
  26. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    567
    No, unfortunately. We prefer solving the issue in the backend compared to exposing a something to specifically do the workaround.

    Yes, we have plans for that, but it is not prioritized right now. For the moment, I would suggest using separate visual elements for the squares (pool them if possible), and use the dynamic transform flag to move the square when the graph is updated. That way, the gpu will set the location of the vertices (no "repaint") if you change the style.translate of the elements.
     
  27. noirb

    noirb

    Joined:
    Apr 10, 2014
    Posts:
    84
    Fair enough. Is this something that we can expect to come in 2022.x sometime?

    I gave this a try, and it's actually a bit slower than using the vector API (at least for simple shapes like squares--I might stick with the VisualElement approach just because it's really nice to be able to customize them in USS, and it makes it much less likely we'll hit that vertex limit).

    Just in case I'm doing something wrong, my approach was:
    * On creation, set a marker's position to
    absolute
    , left/top to zero, and usagehints to
    UsageHints.DynamicTransform

    * Use
    style.opacity
    to hide/show elements when returning/retrieving them from the pool (so they shouldn't trigger any layout updates)
    * Use
    style.translate
    to position them relative to the graph container

    With this, I'm seeing UpdateLayout take more time than usual:
    upload_2023-1-4_18-28-46.png

    Just using the vector API to draw everything, GenerateVisualContent takes almost twice as long but UpdateLayout takes less than half as much time and we come out a little over 10ms ahead:
    upload_2023-1-4_18-30-9.png
     
  28. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    567
    Thanks for sharing your profiling data. It surprises me that the vector API is faster than moving existing visual element. Keep in mind this may change as we optimize under the hood. In 2023.1 we are aiming to land a multi-threaded tessellation/updateVisuals.
     
  29. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    771
    Just to confirm you're not adding/removing anything from the hierarchy when working with your pool?
     
  30. noirb

    noirb

    Joined:
    Apr 10, 2014
    Posts:
    84
    Correct. Pooled items are added to hierarchy on creation, but when being returned to the pool I just set
    element.style.opacity = 0;
    (I'm pretty sure I remember seeing another thread where this was mentioned as the cheapest way to hide an element, but please let me know if that's wrong, haha).

    Here's the actual code I was using in my test:
    Code (CSharp):
    1. // Object pool for marker elements
    2. List<VisualElement> activeMarkers = new List<VisualElement>();
    3. ObjectPool<VisualElement> markerPool = new ObjectPool<VisualElement>(() =>
    4.     {
    5.         var marker = new VisualElement();
    6.         marker.style.position = Position.Absolute;
    7.         marker.style.left = 0;
    8.         marker.style.top = 0;
    9.         marker.style.width = new StyleLength(new Length(16, LengthUnit.Pixel));
    10.         marker.style.height = new StyleLength(new Length(16, LengthUnit.Pixel));
    11.         marker.pickingMode = PickingMode.Ignore;
    12.         marker.usageHints = UsageHints.DynamicTransform;
    13.         marker.AddToClassList(markerUssClassName);
    14.         return marker;
    15.     },
    16.     (marker) =>
    17.     {
    18.         marker.style.opacity = 1f;
    19.     },
    20.     (marker) =>
    21.     {
    22.         marker.style.opacity = 0f;
    23.     },
    24.     (marker) =>
    25.     {
    26.         marker.RemoveFromHierarchy();
    27.     }
    28. );
    29.  
    30. <snip...>
    31.  
    32. void DrawMarkers()
    33. {
    34.     // Hide any elements from the previous update
    35.     for (int i = 0; i < activeMarkers.Count; i++)
    36.     {
    37.         markerPool.Release(activeMarkers[i]);
    38.     }
    39.     activeMarkers.Clear();
    40.  
    41.     foreach (var data in series) // series is Dictionary<string, Vector2>
    42.     {
    43.         for (int i = 0; i < data.Value.Count; i++)
    44.         {
    45.             // don't draw markers that aren't visible
    46.             if (data.Value[i].x < dataRangeX.x || data.Value[i].x > dataRangeX.y)
    47.                 continue;
    48.  
    49.             // get a marker element from the pool
    50.             var marker = markerPool.Get();
    51.  
    52.             // map data values to pixel values relative to content element
    53.             var pos = new Vector2(MapRange(data.Value[i].x, dataRangeX, eleRangeX), MapRange(data.Value[i].y, dataRangeY, eleRangeY));
    54.             marker.style.translate = new StyleTranslate(new Translate(new Length(pos.x-8, LengthUnit.Pixel), new Length(pos.y-8, LengthUnit.Pixel)));
    55.             marker.style.backgroundColor = seriesColors[data.Key] * 0.8f;
    56.  
    57.             // if this is the first time we're using a new marker, add it to the hierarchy
    58.             if (marker.parent == null)
    59.                 content.Add(marker);
    60.  
    61.             // remember this marker so we can release it later
    62.             activeMarkers.Add(marker);
    63.         }
    64.     }
    65. }
    66.  
    The profiling data above was captured with a fixed number of markers visible (and just for a sanity check I added debug logs to the pool create/destroy callbacks, which as expected only logged creation on the first frame then all future updates were just recycling elements from the pool). In this case, the
    DrawMarkers
    function is called with the
    GeometryChangedEvent
    (the parent container is resizable). There is a bit of wasted work where we return elements to the pool just to immediately pull them back out again, but from the profiler it didn't seem to me that this was the bottleneck (I can adjust that, though, if you think it could be the cause).

    We are JUST beginning to evaluate whether 2022.2 is stable enough to migrate our project away from 2021.x, so sadly I think we may not be able to consider moving to 2023.x any time soon... that does sound great, though :)
     
    Last edited: Jan 4, 2023
  31. SamAIns

    SamAIns

    Joined:
    Jan 10, 2023
    Posts:
    2

    Hello, I'm really interested in how you did this.

    I'm looking to create an equivalent of a FCurve panel at runtime.

    Have you any tips or area to start looking at making one?
     
  32. noirb

    noirb

    Joined:
    Apr 10, 2014
    Posts:
    84
    It's still in its infancy, so the code is pretty rough, it's missing some features, and it's not really documented at all, but you can look at my implementation for inspiration if you want.
    DrawChart
    draws the lines, while
    DrawMarkers
    updates the per-point glyphs. To draw or update a line, call
    LineChart.SetData(List<Vector2>)
    . (hopefully the state of both the code and the documentation will improve soon, but I don't have an ETA, sorry) If you use my chart class directly, there are some additional elements built-in to the chart for axis labels and stuff which add some extra padding around the chart, so for your purposes you might want to add
    .nb-chart__axis-label { display: none; }
    to your USS to make the total area of the chart more predictable/easier to control.

    Basic line drawing is really straightforward with the Vector API and I'm quite happy with the performance. For interactive/selectable markers I switched to absolute-positioned VisualElements. If there are a lot it can be kind of expensive to update them all at once if the graph needs to be resized or something so I have some hacks to kind of help with that, but having them as VisualElements makes it really easy to add pointer events, etc., which you could use to make draggable control points and stuff. And unless you're working with hundreds/thousands of points, you probably won't see a major performance hit.
     
    Last edited: Jan 17, 2023
    ririka666 likes this.
  33. Baldrekr

    Baldrekr

    Joined:
    Jul 15, 2018
    Posts:
    12
    In the example code given above to draw a circle, as of Unity 2022.2.5f1 this started flickering (when used to draw a cooldown circle which fills over time). Flickering is solved if you add a ClosePath() statement to the example before calling Fill().
    1. paint2D.fillColor = Color.Blue;
    2. paint2D.BeginPath();
    3. paint2D.Arc(new Vector2(100,100) 50.0f, 0.0f, 360.0f);
    4. paint2D.Fill();
     
  34. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    Thanks, we'll have a look.
     
  35. The-Wand3rer

    The-Wand3rer

    Joined:
    May 14, 2019
    Posts:
    32
    Does the API implement (or are there plans to) an SVG Path syntax parser? Such as "M 50,50 L 100,100"?
    Is it stable enough to use it as a base for a graphics library? I had implemented one using my own code for creating 3D lines, curves, figures, etc. and was considering whether to add support for this.
     
  36. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    No. However, the vector graphics package do provide an SVG parser, but it doesn't use the vector API at the moment. This is a gap we would like to bridge in the next few releases.

    It should be, but it is relatively new and you may encounter some bugs. Please report them!
     
  37. Tauntastic

    Tauntastic

    Joined:
    Sep 24, 2020
    Posts:
    17


    There seems to be loads of issues with Arc. For example, full circle (0 to 360), or in this case, stroke.
     
  38. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    Could you open an issue with the Bug Reporter (Help > Report a Bug...), I couldn't reproduce here, I'll need to use the same arc values as you do.
     
  39. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    741
    When should we use painter2D and when should we use mesh generation API? The unity docs have a demo to make a radial progress bar using mesh gen but the same thing can be done with a painter using the painter.Arc() method its not clear which one we are suppose to use....
     
  40. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    The Painter2D is much more easier to use and provides antialiased primitives. The mesh API is for more advanced use-cases that aren't covered by the Painter2D.

    In short, use the Painter2D whenever you can.

    We do provide a radial progress bar tutorial using the mesh API, but we also provide one using the Painter2D as well:
    https://docs.unity3d.com/2022.2/Documentation/Manual/UIE-radial-progress-use-vector-api.html
     
  41. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    After a double check, this might be fixed in 2022.2.21f1 / 2023.1.0b9, let me know if it isn't.
     
    Tauntastic likes this.
  42. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    741

    There something off with the painter.Arc() function. The arc it returns isn't what you would expect from the method parameters. See screenshots to see what i mean:

    upload_2023-5-17_23-25-10.png

    This is an arc from 180 degrees to 90 degrees in a CW direction but, it should match the red line, but instead the black line is what i get which to me doesn't make sense at all.

    Same for CCW direction with the same angles:

    upload_2023-5-17_23-26-30.png

    Again red line is what i would come to expect. The function should probably change the parameters from StartAngle and EndAngle to StartAngle and DeltaAngle because it seems to be more about displacement from start angle by X angle rather than specifically an angle on the unit circle.

    To be honest both behaviours should be supported depending on what we want. I personally want precise angles on the unit circle it makes more sense to me that way.

    Lastly i found a bug where for some angles the painter glitches and doesn't draw correctly (code is provided at the end):


    a.gif



    Code:
    Code (CSharp):
    1.     private void Generate(MeshGenerationContext ctx)
    2.     {
    3.         float width = contentRect.width;
    4.         float height = contentRect.height;
    5.         float radius = Mathf.Min(width, height) / 2f;
    6.         var painter = ctx.painter2D;
    7.         painter.lineWidth = LineWidth;
    8.  
    9.         painter.BeginPath();
    10.         painter.Arc(new(width * .5f, height * .5f), radius - LineWidth / 2f, StartAngle, EndAngle, _direction);
    11.         painter.strokeColor = _color;
    12.         painter.Stroke();
    13.  
    14.     }
     
  43. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    The UI Toolkit coordinate system is Y-down, which means that 90deg is at the bottom of the circle. I think this is where your confusion comes from.

    Which Unity version are you using? We fixed something similar recently.
     
    Last edited: May 18, 2023
  44. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    741
    Isn't that like saying some software uses Y down so Vector3.Up points down. That would just be silly. It's not very mathematically accurate currently so should be corrected and the function should do what you mathematically expect.

    Why not just reuse the Handles.DrawSolidArc math and flip the y value so the output is what people expect the output to be?

    Eg restrict the method's angles to be between 0 and 360 using modulo %360 then have this bit of logic:

    Code (csharp):
    1.  
    2. //if CCW the final angle needs to be bigger than the start angle
    3. if(_direction == CounterClockwise && endAngle < startAngle)
    4.     endAngle +=360;
    5.  
    6. //if CW the final angle need to be smaller than the start angle
    7. else if(_directioon == Clockwise && endAngle > startAngle)
    8.    endAngle -= 360;
    9.  
    10. //followed by some kinda lerp between the two angles to draw the arc
    11.  
    12. //start point
    13. x1 = cos(startAngle);
    14. y1 = -sin(startAngle);
    15.  
    16. //end point
    17. x2 = cos(endAngle);
    18. y2 = -sin(endAngle);
    19.  
    And flip the y of course during output this will now draw arcs to and from angles in the direction users want.


    As for the unity version i am on unity 2022.2.19f - the glitch only occurs in CCW direction which suggests to me the normals are getting flipped during the generation causing the mesh to look weird, so might be good idea to look to check if the indices are correctly ordered for the mesh triangles. (I had the same issue with my spline mesh generation).
     
    Last edited: May 18, 2023
  45. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    The Unity world space is indeed Y-up, but the UI Toolkit coordinate system is based on Flexbox, which is a web layout model. The UI Toolkit origin is at the top-left of the document, with the positive Y direction going down. Following that convention, the Painter2D Arc() methods behave very closely to what the HTML canvas performs:
    https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc

    We have quite a few Painter2D fixes that will be available in the upcoming 2022.2.21f version. Keep an eye for that release, and please report a bug if it doesn't fix the glitches.

    EDIT: I was able to reproduce with a counter-clockwise arc, we're looking into it. Thanks for reporting!
     
    Last edited: May 19, 2023
    mariandev likes this.
  46. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    741

    Even if the HTML canvas does it doesn't mean you should - you should aim to keep it in line with pure mathematics in my opinion because thats also what unity's XY plane does for 2D games and they should be consistent especially when you add world space UI support. Think of all the annoying remapping we have to do from game data values to ui values because of the coordinate system for arcs :(

    If you asked people to draw an arc from 180 degrees to 90 degrees CCW, 99% people would expected to draw an arc starting at (-1,0) to (0,1) the way my red line was drawn in the image.

    I would expect for example the sin function to give a positive Y result not a negative Y regardless of the coordinate system used. Alternatively if you don't want to change the behaviour could you add a "UnitCircleArc" method that will adhere to pure math with the correct Y sign and positions on the circle for the angles?

    And, i am glad you could reproduce the bug! :)
     
  47. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    Adding a new method is something we can consider since modifying the behavior of the current Arc() method is a breaking change. The good news is you can add an extension method in your project that provides the behavior you expect right now. Something like this:

    Code (CSharp):
    1.  
    2. public static class Painter2DExtensions
    3. {
    4.     public static void UnitCircleArc(this Painter2D painter, Vector2 center, float radius, Angle startAngle, Angle endAngle, ArcDirection dir)
    5.     {
    6.         painter.Arc(center, radius, -startAngle.ToDegrees(), -endAngle.ToDegrees(), dir);
    7.     }
    8. }
    9.  
    You can drop that in your project and make calls such as:
    Code (CSharp):
    1. paint.UnitCircleArc(new Vector2(100, 100), 50, 0.0f, 45, ArcDirection.CounterClockwise);
    Hope this helps!
     
    Hellfim likes this.
  48. lczyzycki7lvls

    lczyzycki7lvls

    Joined:
    Sep 10, 2018
    Posts:
    8
    Is it considered an expected behavior for rounded line caps to not work with paths drawn with
    BezierCurveTo
    or is it worth reporting as a bug?
     
  49. mcoted3d

    mcoted3d

    Unity Technologies

    Joined:
    Feb 3, 2016
    Posts:
    998
    Rounded caps should definitely work with BezierCurveTo(), please report!
     
    lczyzycki7lvls likes this.
  50. AndreaGalet

    AndreaGalet

    Joined:
    May 21, 2020
    Posts:
    90
    Can you help me with this code, i don't get why the vectorImage has some "border" on the UI Toolkit:


    Code (CSharp):
    1.  [MenuItem("Assets/Create/CharacterMask")]
    2.     public static void DrawCharacterMask()
    3.     {
    4.         var painter = new Painter2D()
    5.         {
    6.             fillColor = Color.green,
    7.             lineCap = LineCap.Butt,
    8.             lineJoin = LineJoin.Miter,
    9.             lineWidth = 0,
    10.             miterLimit = 0,
    11.             strokeColor = Color.red
    12.         };
    13.      
    14.         painter.BeginPath();
    15.         painter.MoveTo(new Vector2(0, 0));
    16.         painter.LineTo(new Vector2(300, 0));
    17.         painter.LineTo(new Vector2(300, 250));
    18.         painter.LineTo(new Vector2(250, 300));
    19.         painter.LineTo(new Vector2(0, 300));
    20.         painter.ClosePath();
    21.         painter.Fill();
    22.         painter.Stroke();
    23.         var vectorImage = ScriptableObject.CreateInstance<VectorImage>();
    24.         painter.SaveToVectorImage(vectorImage);
    25.         AssetDatabase.CreateAsset(vectorImage, "Assets/CharacterMask.asset");
    26.         AssetDatabase.SaveAssets();
    27.  
    28.         EditorUtility.FocusProjectWindow();
    29.  
    30.         Selection.activeObject = vectorImage;
    31.      
    32.         painter.Dispose();
    33.     }
    upload_2023-6-22_17-44-37.png
    upload_2023-6-22_17-45-41.png