Search Unity

Feature Request Skew Support

Discussion in 'UI Toolkit' started by BinaryCats, Jan 27, 2020.

  1. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    Hi,

    In my previous/current title, our UI had a skew to it
    upload_2020-1-27_12-12-54.png
    and boy oh boy was it a pain. All of our UI had this skew baked into the image. We will not do this way again!

    (likely, if we do a skew, we will likely have the raw image not have the skew and alter
    OnPopulateMesh
    to skew the element
    https://cdn.discordapp.com/attachments/236733544569765892/602235568134422562/skew.gif
    )

    Anyway, I digress.

    The issue with baking the skew in the image is when it comes to reducing texture size. Ideally box above could technically be 10px by 10px (only corners), then the middle stretched to make the box fit the required size. Unfortunately because of the X skew, the texture can not be scaled on the Y, without artifacting (as it would alter the angle of the skew)

    I would like to suggest that unity support the .css skew selector to make UI's with skews easy to work with.

    Thanks for listening :)
     
  2. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
  3. unity_zIYWJyNr2AiCPA

    unity_zIYWJyNr2AiCPA

    Joined:
    Jan 5, 2020
    Posts:
    9
    Could the skew property be supported in the future?
     
    MousePods, niuage, cgascons and 3 others like this.
  4. iperezipina

    iperezipina

    Joined:
    Jun 24, 2021
    Posts:
    26
    Any examples on how to use generateVisualContent to import transform data of visual element?
    thanks in advance
     
  5. Xan_9

    Xan_9

    Joined:
    Oct 7, 2020
    Posts:
    31
    Also looking for an example on how to use generateVisualContent to modify images.
     
  6. Xan_9

    Xan_9

    Joined:
    Oct 7, 2020
    Posts:
    31
    Made a simple skew functionality using generateVisualContent, and thought I'd share it:

    Most of allocation code taken from here: https://docs.unity3d.com/2022.2/Documentation/ScriptReference/UIElements.MeshGenerationContext.html

    Code (CSharp):
    1. public class SkewedElement : VisualElement
    2. {
    3.  
    4. private readonly Vertex[] _vertices = new Vertex[4];
    5. private readonly ushort[] _indices = { 0, 1, 2, 2, 3, 0 };
    6.  
    7. public Texture2D Texture { get; private set; }
    8. public float SkewX { get; set; }
    9. public float SkewY { get; set; }
    10.  
    11. public SkewedElement(Texture2D texture)
    12. {
    13.     Texture = texture;
    14.     generateVisualContent += OnGenerateVisualContent;
    15.  
    16.     _vertices[0].tint = Color.white;
    17.     _vertices[1].tint = Color.white;
    18.     _vertices[2].tint = Color.white;
    19.     _vertices[3].tint = Color.white;
    20. }
    21. private void OnGenerateVisualContent(MeshGenerationContext mgc)
    22. {
    23.     if (contentRect.width < 0.01f || contentRect.height < 0.01f)
    24.         return;
    25.  
    26.     _vertices[0].position = new Vector3(contentRect.x, contentRect.height, Vertex.nearZ);
    27.     _vertices[1].position = new Vector3(contentRect.x, contentRect.y, Vertex.nearZ);
    28.     _vertices[2].position = new Vector3(contentRect.width, contentRect.y, Vertex.nearZ);
    29.     _vertices[3].position = new Vector3(contentRect.width, contentRect.height, Vertex.nearZ);
    30.  
    31.     var mwd = mgc.Allocate(_vertices.Length, _indices.Length, Texture);
    32.  
    33.     var uvRegion = mwd.uvRegion;
    34.     _vertices[0].uv = new Vector2(0, 0) * uvRegion.size + uvRegion.min;
    35.     _vertices[1].uv = new Vector2(0, 1) * uvRegion.size + uvRegion.min;
    36.     _vertices[2].uv = new Vector2(1, 1) * uvRegion.size + uvRegion.min;
    37.     _vertices[3].uv = new Vector2(1, 0) * uvRegion.size + uvRegion.min;
    38.  
    39.     //Skew
    40.     SkewImage(mwd.vertexCount);
    41.  
    42.     mwd.SetAllVertices(_vertices);
    43.     mwd.SetAllIndices(_indices);
    44. }
    45.  
    46. private void SkewImage(float vertexCount)
    47. {
    48.     var xskew = contentRect.height * Mathf.Tan(Mathf.Deg2Rad * SkewX);
    49.     var yskew = contentRect.width * Mathf.Tan(Mathf.Deg2Rad * SkewY);
    50.  
    51.     for (int i = 0; i < vertexCount; i++)
    52.     {
    53.         for (int j = 0; j < _vertices.Length; j++)
    54.         {
    55.             _vertices[j].position += new Vector3(
    56.                 Mathf.Lerp(0, xskew, (_vertices[j].position.y - contentRect.y) / contentRect.height),
    57.                 Mathf.Lerp(0, yskew, (_vertices[j].position.x - contentRect.x) / contentRect.width));
    58.         }
    59.     }
    60. }
    61. }
    If there is a better way, I'd love to hear it.
     
  7. TeorikDeli

    TeorikDeli

    Joined:
    Apr 6, 2014
    Posts:
    149
    Hmmm... I tried to extend @Xan_9 's code above. But it seems, I can't override the VisualElement's mesh; generateVisualContent only creates a new mesh. For example, I am also trying to skew a box which has radius and I don't want to use images (because I want to easily animate these boxes). Is there anyway to override the VisualElement's mesh? Or, at least get the VisualElement as Texture2D? I might use Texture2D in the mgc.Allocate(), then (maybe) hide the original VisualElement. I can't find anything in the docs. OR, could you give us any info about when will the UI Toolkit have the skew property? :D

    An example (just like in the thread's main post)
    Screen Shot 2022-09-28 at 13.42.01.png

    my extended version (which is usable in the UI Builder, but not finished)

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3.  
    4. public class SkewedElement : VisualElement
    5. {
    6.     private readonly Vertex[] _vertices = new Vertex[4];
    7.     private readonly ushort[] _indices  = {0, 1, 2, 2, 3, 0};
    8.  
    9.     [UnityEngine.Scripting.Preserve]
    10.     public new class UxmlFactory : UxmlFactory<SkewedElement, UxmlTraits> {}
    11.  
    12.     [UnityEngine.Scripting.Preserve]
    13.     public new class UxmlTraits : VisualElement.UxmlTraits
    14.     {
    15.         private readonly UxmlFloatAttributeDescription skewX = new()
    16.                                                                {
    17.                                                                    name         = "skew-x",
    18.                                                                    defaultValue = 0
    19.                                                                };
    20.  
    21.         private readonly UxmlFloatAttributeDescription skewY = new()
    22.                                                                {
    23.                                                                    name         = "skew-y",
    24.                                                                    defaultValue = 0
    25.                                                                };
    26.  
    27.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    28.         {
    29.             base.Init(ve, bag, cc);
    30.             var elm = ve as SkewedElement;
    31.  
    32.             elm.SkewX = skewX.GetValueFromBag(bag, cc);
    33.             elm.SkewY = skewY.GetValueFromBag(bag, cc);
    34.         }
    35.     }
    36.  
    37.     public float SkewX { get; private set; }
    38.     public float SkewY { get; private set; }
    39.  
    40.     public SkewedElement()
    41.     {
    42.         generateVisualContent += OnGenerateVisualContent;
    43.     }
    44.  
    45.     private void OnGenerateVisualContent(MeshGenerationContext mgc)
    46.     {
    47.         var rect = BorderedRect();
    48.         if(rect.width < 0.01f || rect.height < 0.01f)
    49.             return;
    50.  
    51.         var color = resolvedStyle.backgroundColor;
    52.         _vertices[0].tint = color;
    53.         _vertices[1].tint = color;
    54.         _vertices[2].tint = color;
    55.         _vertices[3].tint = color;
    56.  
    57.         _vertices[0].position = new Vector3(rect.x, rect.height, Vertex.nearZ);
    58.         _vertices[1].position = new Vector3(rect.x, rect.y, Vertex.nearZ);
    59.         _vertices[2].position = new Vector3(rect.width, rect.y, Vertex.nearZ);
    60.         _vertices[3].position = new Vector3(rect.width, rect.height, Vertex.nearZ);
    61.  
    62.         var mwd = mgc.Allocate(_vertices.Length, _indices.Length);
    63.  
    64.         var uvRegion = mwd.uvRegion;
    65.         _vertices[0].uv = new Vector2(0, 0) * uvRegion.size + uvRegion.min;
    66.         _vertices[1].uv = new Vector2(0, 1) * uvRegion.size + uvRegion.min;
    67.         _vertices[2].uv = new Vector2(1, 1) * uvRegion.size + uvRegion.min;
    68.         _vertices[3].uv = new Vector2(1, 0) * uvRegion.size + uvRegion.min;
    69.  
    70.         //Skew
    71.         SkewImage(mwd.vertexCount, rect);
    72.  
    73.         mwd.SetAllVertices(_vertices);
    74.         mwd.SetAllIndices(_indices);
    75.     }
    76.  
    77.     private void SkewImage(float vertexCount, Rect rect)
    78.     {
    79.         var xskew = rect.height * Mathf.Tan(Mathf.Deg2Rad * SkewX);
    80.         var yskew = rect.width * Mathf.Tan(Mathf.Deg2Rad * SkewY);
    81.  
    82.         for(int i = 0; i < vertexCount; i++)
    83.         {
    84.             for(int j = 0; j < _vertices.Length; j++)
    85.             {
    86.                 _vertices[j].position += new Vector3(Mathf.Lerp(0, xskew, (_vertices[j].position.y - rect.y) / rect.height),
    87.                                                      Mathf.Lerp(0, yskew, (_vertices[j].position.x - rect.x) / rect.width));
    88.             }
    89.         }
    90.     }
    91.  
    92.     private Rect BorderedRect()
    93.     {
    94.         var leftWidth  = resolvedStyle.borderLeftWidth;
    95.         var topWidth   = resolvedStyle.borderTopWidth;
    96.         var borderRect = new Rect(new Vector2(-leftWidth, -topWidth), new Vector2(leftWidth + resolvedStyle.borderRightWidth, topWidth + resolvedStyle.borderBottomWidth));
    97.         return new Rect(paddingRect.position + borderRect.position, paddingRect.size + borderRect.size);
    98.     }
    99. }
     
    SimonDufour likes this.
  8. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    572
    Possibly the UITK vector API could help you constructing your shape instead of manipulating/creating the vertices directly. Depending on what you are doing the texturing options may be lacking in the API to use it. We intend on adding ways to completely override the mesh/mask of the visual element, but there is no ETA yet.
     
    TeorikDeli likes this.
  9. TeorikDeli

    TeorikDeli

    Joined:
    Apr 6, 2014
    Posts:
    149
    I will definitely use the Vector API when we update our project to Unity 2022.x. It is not in preview anymore, right? Texturing not a problem for us at the moment, but it'd be nice to have '=D By the way, is there transition/animation feature in the Vector API, or should we use coroutines, etc?
     
  10. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    I did a skewed Health Bar sample using Vector API with animations. Performance was good.



    Source code: https://github.com/DragonGround/Ove...34489bc9afe886130232ff2037/CharacterStats.tsx

    (It's written in TS with OneJS, a tool that allows you to use TS for UI Toolkit)
     
    TeorikDeli likes this.
  11. TeorikDeli

    TeorikDeli

    Joined:
    Apr 6, 2014
    Posts:
    149
    Sooo, I added rounded corner feature and moved all skew properties to USS. I am using USS color property for tint/background color, border-radius properties for rounded corners, background-image property for background image. --skew-x for x-directional skew, --skew-y for y-directional skew, --skew-background for skewing or not skewing background image. I am also using transform-origin values for skew point. There is no antialiasing and transition for skew values (I added a simple solution, then removed "
    _skewX = Mathf.Lerp(_skewX, SkewX, 0.5f);
    "). I couldn't find a solution for these two in a simple way (with using UI Toolkit). By the way, I tried
    IVisualElementScheduledItem
    , but didn't like the result, lerping is much easier. Also, I just simply stretching background, I don't need any variation for the background-image.

    I used the RoundedQuadMesh sample from Bunny83's answer and simplified it for what I need.

    If I find a way (or if someone helps me) to easily use transition for skew properties in the future, I'd update this post. I think CustomStyleProperty can be automatically support transitions for types like float, int, Color.


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3.  
    4. public class SkewedElement : VisualElement
    5. {
    6.     [UnityEngine.Scripting.Preserve]
    7.     public new class UxmlFactory : UxmlFactory<SkewedElement, UxmlTraits> {}
    8.  
    9.     static readonly CustomStyleProperty<float> S_SkewX  = new CustomStyleProperty<float>("--skew-x");
    10.     static readonly CustomStyleProperty<float> S_SkewY  = new CustomStyleProperty<float>("--skew-y");
    11.     static readonly CustomStyleProperty<bool>  S_SkewBG = new CustomStyleProperty<bool>("--skew-background");
    12.  
    13.     public float SkewX  { get; private set; }
    14.     public float SkewY  { get; private set; }
    15.     public bool  SkewBG { get; private set; }
    16.  
    17.     private Vertex[] _vertices = {};
    18.     private ushort[] _indices  = {};
    19.  
    20.     public SkewedElement()
    21.     {
    22.         RemoveBGTint();
    23.         generateVisualContent += OnGenerateVisualContent;
    24.         RegisterCallback<CustomStyleResolvedEvent>(OnStyleResolved);
    25.     }
    26.  
    27.     private void OnStyleResolved(CustomStyleResolvedEvent evt)
    28.     {
    29.         evt.customStyle.TryGetValue(S_SkewX, out var skewX);
    30.         evt.customStyle.TryGetValue(S_SkewY, out var skewY);
    31.         evt.customStyle.TryGetValue(S_SkewBG, out var skewBG);
    32.         SkewX  = skewX;
    33.         SkewY  = skewY;
    34.         SkewBG = skewBG;
    35.  
    36.         RemoveBGTint();
    37.     }
    38.  
    39.     private void OnGenerateVisualContent(MeshGenerationContext mgc)
    40.     {
    41.         var rect = BorderedRect();
    42.         if(rect.width < 0.01f || rect.height < 0.01f)
    43.             return;
    44.  
    45.         var backgroundColor   = resolvedStyle.color;
    46.         var radiusTopLeft     = resolvedStyle.borderTopLeftRadius;
    47.         var radiusTopRight    = resolvedStyle.borderTopRightRadius;
    48.         var radiusBottomLeft  = resolvedStyle.borderBottomLeftRadius;
    49.         var radiusBottomRight = resolvedStyle.borderBottomRightRadius;
    50.  
    51.         var biggestRadius = radiusTopLeft;
    52.  
    53.         if(radiusTopRight > biggestRadius)
    54.             biggestRadius = radiusTopRight;
    55.         if(radiusBottomLeft > biggestRadius)
    56.             biggestRadius = radiusBottomLeft;
    57.         if(radiusBottomRight > biggestRadius)
    58.             biggestRadius = radiusBottomRight;
    59.  
    60.         var cornerSegments = Mathf.Clamp(Mathf.RoundToInt(biggestRadius / 2) + 3, 5, 12);
    61.         var vCount         = cornerSegments * 4 + 1;
    62.  
    63.         _vertices = new Vertex[vCount];
    64.  
    65.         var f        = 1f / (cornerSegments - 1);
    66.         var sPI      = Mathf.PI * 0.5f * f;
    67.         var rW       = rect.width * 0.5f;
    68.         var rH       = rect.height * 0.5f;
    69.         var shortest = rW < rH ? rW : rH;
    70.  
    71.         var topLeft     = new Vector3(rect.x, rect.y, Vertex.nearZ);
    72.         var topRight    = new Vector3(rect.width, rect.y, Vertex.nearZ);
    73.         var bottomLeft  = new Vector3(rect.x, rect.height, Vertex.nearZ);
    74.         var bottomRight = new Vector3(rect.width, rect.height, Vertex.nearZ);
    75.         var radiusTL    = Mathf.Clamp(radiusTopLeft, -shortest, shortest);
    76.         var radiusTR    = Mathf.Clamp(radiusTopRight, -shortest, shortest);
    77.         var radiusBL    = Mathf.Clamp(radiusBottomLeft, -shortest, shortest);
    78.         var radiusBR    = Mathf.Clamp(radiusBottomRight, -shortest, shortest);
    79.  
    80.         _vertices[0].tint     = backgroundColor;
    81.         _vertices[0].position = new Vector3(rW, rH, Vertex.nearZ);
    82.         _vertices[0].uv       = new Vector2(rW / rect.width, rH / rect.height);
    83.  
    84.         for(int i = 0; i < cornerSegments; i++)
    85.         {
    86.             var sin = Mathf.Sin(i * sPI);
    87.             var cos = Mathf.Cos(i * sPI);
    88.             var v1  = topLeft + new Vector3(radiusTL - cos * radiusTL, radiusTL - sin * radiusTL);
    89.             var v2  = topRight + new Vector3(-radiusTR + sin * radiusTR, radiusTR - cos * radiusTR);
    90.             var v3  = bottomRight + new Vector3(-radiusBR + cos * radiusBR, -radiusBR + sin * radiusBR);
    91.             var v4  = bottomLeft + new Vector3(radiusBL - sin * radiusBL, -radiusBL + cos * radiusBL);
    92.  
    93.             var i1 = 1 + i;
    94.             var i2 = cornerSegments + i1;
    95.             var i3 = cornerSegments * 2 + i1;
    96.             var i4 = cornerSegments * 3 + i1;
    97.  
    98.             _vertices[i1].tint = backgroundColor;
    99.             _vertices[i2].tint = backgroundColor;
    100.             _vertices[i3].tint = backgroundColor;
    101.             _vertices[i4].tint = backgroundColor;
    102.  
    103.             _vertices[i1].position = v1;
    104.             _vertices[i2].position = v2;
    105.             _vertices[i3].position = v3;
    106.             _vertices[i4].position = v4;
    107.  
    108.             if(SkewBG)
    109.             {
    110.                 _vertices[i1].uv = new Vector2(v1.x / rect.width, 1 - v1.y / rect.height);
    111.                 _vertices[i2].uv = new Vector2(v2.x / rect.width, 1 - v2.y / rect.height);
    112.                 _vertices[i3].uv = new Vector2(v3.x / rect.width, 1 - v3.y / rect.height);
    113.                 _vertices[i4].uv = new Vector2(v4.x / rect.width, 1 - v4.y / rect.height);
    114.             }
    115.         }
    116.  
    117.         var triCount = cornerSegments * 4;
    118.         _indices = new ushort[triCount * 3];
    119.  
    120.         for(int i = 0; i < triCount; i++)
    121.         {
    122.             _indices[i * 3]     = 0;
    123.             _indices[i * 3 + 1] = (ushort) (i + 1);
    124.             _indices[i * 3 + 2] = (ushort) (i + 2);
    125.         }
    126.  
    127.         _indices[triCount * 3 - 1] = 1;
    128.  
    129.         SkewRect(rect);
    130.  
    131.         var mwd = mgc.Allocate(_vertices.Length, _indices.Length, resolvedStyle.backgroundImage.texture);
    132.         mwd.SetAllVertices(_vertices);
    133.         mwd.SetAllIndices(_indices);
    134.     }
    135.  
    136.     private void SkewRect(Rect rect)
    137.     {
    138.         var xOrigin = resolvedStyle.transformOrigin.x / rect.width;
    139.         var yOrigin = resolvedStyle.transformOrigin.y / rect.height;
    140.         var xSkew   = rect.height * Mathf.Tan(Mathf.Deg2Rad * SkewX);
    141.         var ySkew   = rect.width * Mathf.Tan(Mathf.Deg2Rad * SkewY);
    142.  
    143.         for(int i = 0; i < _vertices.Length; i++)
    144.         {
    145.             var xLerp = Mathf.LerpUnclamped(0, xSkew, (_vertices[i].position.y - rect.y) / rect.height);
    146.             var yLerp = Mathf.LerpUnclamped(0, ySkew, (_vertices[i].position.x - rect.x) / rect.width);
    147.             _vertices[i].position += new Vector3(xLerp - xSkew * xOrigin, yLerp - ySkew * yOrigin);
    148.  
    149.             if(!SkewBG)
    150.                 _vertices[i].uv = new Vector2(_vertices[i].position.x / rect.width, 1 - _vertices[i].position.y / rect.height);
    151.         }
    152.     }
    153.  
    154.     private Rect BorderedRect()
    155.     {
    156.         var leftWidth  = resolvedStyle.borderLeftWidth;
    157.         var topWidth   = resolvedStyle.borderTopWidth;
    158.         var borderRect = new Rect(new Vector2(-leftWidth, -topWidth), new Vector2(leftWidth + resolvedStyle.borderRightWidth, topWidth + resolvedStyle.borderBottomWidth));
    159.         return new Rect(paddingRect.position + borderRect.position, paddingRect.size + borderRect.size);
    160.     }
    161.  
    162.     private void RemoveBGTint()
    163.     {
    164.         var bgTintColor = new StyleColor {value = Color.clear};
    165.         style.unityBackgroundImageTintColor = bgTintColor;
    166.     }
    167. }
    Sample USS code
    Code (CSharp):
    1. SkewedElement {
    2.     border-top-left-radius: 10px;
    3.     border-bottom-left-radius: 10px;
    4.     border-top-right-radius: 10px;
    5.     border-bottom-right-radius: 10px;
    6.     color: rgb(254, 0, 0);
    7.     width: 100px;
    8.     height: 100px;
    9.     position: absolute;
    10.     left: 922px;
    11.     top: 230px;
    12.     transition-duration: 0.5s;
    13.     --skew-x: 10;
    14.     --skew-y: 0;
    15. }
    16.  
    17. SkewedElement:hover {
    18.     border-top-left-radius: 20px;
    19.     border-bottom-left-radius: 20px;
    20.     border-top-right-radius: 20px;
    21.     border-bottom-right-radius: 20px;
    22.     width: 100px;
    23.     height: 100px;
    24.     color: rgb(217, 217, 217);
    25.     --skew-x: 40;
    26.     --skew-y: 0;
    27.     --skew-background: true;
    28. }
     
    Strategos likes this.