using Rive.Utils; using UnityEngine; namespace Rive.Components { /// /// Holds the data for a render object's transform in Rive coordinates. Rive uses a cartesian coordinate system where the positive x-axis extends towards the right, and the positive y-axis extends towards the bottom of the screen. The origin (0,0) is located at the top left corner of the screen. /// public struct RenderTransform { private Vector2 m_position; private Vector2 m_size; private float m_rotation; private Vector2 m_scale; private Vector2 m_pivot; private static readonly Vector3[] s_widgetCorners = new Vector3[4]; private static readonly Vector3[] s_panelCorners = new Vector3[4]; // Top-left corner in Rive coordinates /// /// The position of the Transform in Rive coordinates within it's panel /// public Vector2 Position => m_position; /// /// The dimensions of the Transform; /// public Vector2 Size => m_size; /// /// The rotation of the Transform in degrees /// public float Rotation => m_rotation; /// /// The scale of the Transform within it's parent panel /// public Vector2 Scale => m_scale; /// /// The pivot point of the Transform /// public Vector2 Pivot => m_pivot; /// /// Creates a new RenderTransform with the given position, size, rotation, scale, and pivot. /// /// The position of the Transform in Rive coordinates within it's panel. /// The dimensions of the Transform. /// The rotation of the Transform in degrees. /// The scale of the Transform within it's parent panel. /// The pivot point of the Transform. public RenderTransform(Vector2 position, Vector2 size, float rotation, Vector2 scale, Vector2 pivot) { m_position = position; m_size = size; m_rotation = rotation; m_scale = scale; m_pivot = pivot; } /// /// Creates a new RenderTransform from the given RectTransform. This method will convert the RectTransform's position, size, rotation, scale, and pivot to Rive coordinates. /// /// The RectTransform to create the RenderTransform from. /// A new RenderTransform with the given RectTransform's data. public static RenderTransform FromRectTransform(RectTransform rectTransform, RectTransform panelRectTransform) { if (rectTransform == null || panelRectTransform == null) { DebugLogger.Instance.LogError("FromRectTransform called with null rectTransform or panelRectTransform"); return default; } // Calculate adjusted data if pivot is not (0,0) RectTransformPivotUtility.RectTransformData adjustedData = rectTransform.pivot != Vector2.zero ? RectTransformPivotUtility.CalculatePivotChange(rectTransform, Vector2.zero) : new RectTransformPivotUtility.RectTransformData(rectTransform.pivot, rectTransform.anchoredPosition, rectTransform.localPosition); Vector2 size = rectTransform.rect.size; bool shouldFlip = TextureHelper.ShouldFlipTexture(); Matrix4x4 parentMatrix = panelRectTransform.localToWorldMatrix; Matrix4x4 inverseParentMatrix = parentMatrix.inverse; Matrix4x4 childMatrix = rectTransform.localToWorldMatrix; Matrix4x4 relativeMatrix = inverseParentMatrix * childMatrix; // Get the world position of the widget and panel corners // We use world positions for our calculations to account for the rect transform being in a complicated heirarchy // Before we pass the transform info to the Rive renderer, we essentially flatten the hierarchy and render the widgets in absolute space to simplify the matrix calculations. rectTransform.GetWorldCorners(s_widgetCorners); panelRectTransform.GetWorldCorners(s_panelCorners); Vector3 widgetWorldPosTL = s_widgetCorners[1]; Vector3 panelWorldPosTL = s_panelCorners[1]; // Calculate relative position in world space Vector3 relativeWorldPos = widgetWorldPosTL - panelWorldPosTL; // Convert world space difference to panel's local space Vector3 relativeLocalPos = inverseParentMatrix.MultiplyVector(relativeWorldPos); Vector2 position = new Vector2(relativeLocalPos.x, relativeLocalPos.y); // Adjust Y position if flipping is required based on the texture coordinate system if (shouldFlip) { position.y = panelRectTransform.rect.size.y + position.y + rectTransform.rect.height; } else { position.y = panelRectTransform.rect.size.y - position.y - panelRectTransform.rect.height; } // Extract rotation (in radians) from the relative matrix float rotation = Mathf.Atan2(relativeMatrix.m01, relativeMatrix.m00); // Extract scale from the relative matrix Vector2 scale = new Vector2( new Vector2(relativeMatrix.m00, relativeMatrix.m01).magnitude, new Vector2(relativeMatrix.m10, relativeMatrix.m11).magnitude ); Vector2 pivot = adjustedData.Pivot; return new RenderTransform(position, size, rotation, scale, pivot); } } /// /// We use this to calculate pivot changes for RectTransforms while maintaining the world position. /// internal class RectTransformPivotUtility { public struct RectTransformData { public Vector2 Pivot; public Vector2 AnchoredPosition; public Vector3 LocalPosition; public RectTransformData(Vector2 pivot, Vector2 anchoredPosition, Vector3 localPosition) { Pivot = pivot; AnchoredPosition = anchoredPosition; LocalPosition = localPosition; } } public static RectTransformData CalculatePivotChange(RectTransform rectTransform, Vector2 newPivot) { Vector2 size = rectTransform.rect.size; // Get the offset in anchored position Vector2 pivotDelta = newPivot - rectTransform.pivot; Vector2 positionDelta = new Vector2( pivotDelta.x * size.x, pivotDelta.y * size.y ); Vector2 newAnchoredPosition = rectTransform.anchoredPosition - positionDelta; // Calculate new local position (to maintain world position) Vector3 localPositionDelta = rectTransform.localRotation * new Vector3(positionDelta.x, positionDelta.y, 0); Vector3 newLocalPosition = rectTransform.localPosition + localPositionDelta; return new RectTransformData(newPivot, newAnchoredPosition, newLocalPosition); } public static void ApplyPivotChange(RectTransform rectTransform, RectTransformData data) { rectTransform.pivot = data.Pivot; rectTransform.anchoredPosition = data.AnchoredPosition; rectTransform.localPosition = data.LocalPosition; } public static void SetPivot(RectTransform rectTransform, Vector2 newPivot) { RectTransformData newData = CalculatePivotChange(rectTransform, newPivot); ApplyPivotChange(rectTransform, newData); } } }