using System.Collections.Generic; using Rive.Utils; using UnityEngine; namespace Rive.Components { /// /// Utility class for performing hit testing (raycasting) on RivePanels to detect which widgets are under a given point. /// This is used to handle input events and determine which widgets should receive pointer interactions. /// public class PanelRaycaster { /// /// Populates the raycastResults list with the widgets in a RivePanel that are hit by the given normalized local point in the panel. /// /// The RivePanel to check for hit widgets. /// The normalized local point in the panel to check for hit widgets. The coordinates are in the range [0,1] where (0,0) is the bottom-left corner and (1,1) is the top-right corner. /// The camera used by the event system or canvas /// The list to populate with hit widgets. public static void RaycastAll(IRivePanel rivePanel, Vector2 normalizedPointInPanel, List raycastResults) { RectTransform panelRectTransform = rivePanel.WidgetContainer; if (panelRectTransform == null) { DebugLogger.Instance.LogError("Panel RectTransform is null."); return; } for (int i = rivePanel.Widgets.Count - 1; i >= 0; i--) { var widget = rivePanel.Widgets[i]; if (widget == null || !widget.Enabled || widget.RenderObject == null || widget.HitTestBehavior == HitTestBehavior.None) continue; Vector2 normalizedWidgetPoint; bool isWithinWidgetBounds = TryGetNormalizedPointInWidget(rivePanel, normalizedPointInPanel, widget, out normalizedWidgetPoint); if (ProcessHitTestBehavior(widget, normalizedWidgetPoint, raycastResults, isWithinWidgetBounds)) { return; } } } /// /// Processes the hit test behavior of a widget and adds it to the raycastResults list if it should be hit. /// /// The widget to process. /// The normalized point in the widget's rect to check for a hit. The coordinates are in the range [0,1] where (0,0) is the bottom-left corner and (1,1) is the top-right corner. /// The list to populate with hit widgets. /// True if should should block other widgets from being hit, false otherwise. private static bool ProcessHitTestBehavior(IRiveWidget widget, Vector2 normalizedPointInWidgetRect, List raycastResults, bool isWithinWidgetBounds) { switch (widget.HitTestBehavior) { case HitTestBehavior.Opaque: raycastResults.Add(widget); // Block other widgets from being hit if the pointer is within the widget return isWithinWidgetBounds; case HitTestBehavior.Translucent: bool foundHit = widget.HitTest(normalizedPointInWidgetRect); if (foundHit) { raycastResults.Add(widget); return isWithinWidgetBounds; } break; #pragma warning disable CS0618 // Transparent hit testing is deprecated but kept for backward compatibility case HitTestBehavior.Transparent: raycastResults.Add(widget); // Continue checking other widgets return false; #pragma warning restore CS0618 case HitTestBehavior.None: // Do not add to raycastResults return false; } return false; } /// /// Tries to get the normalized local point in the widget from the normalized local point in the panel. /// /// The RivePanel that contains the widget. /// The normalized local point in the panel. The coordinates are in the range [0,1] where (0,0) is the bottom-left corner and (1,1) is the top-right corner. /// The widget to get the normalized local point in. /// The normalized point in the widget's rect. /// True if the normalized local point is within the widget's bounds, false otherwise. public static bool TryGetNormalizedPointInWidget(IRivePanel rivePanel, Vector2 normalizedPointInPanel, IRiveWidget widget, out Vector2 normalizedWidgetPoint) { normalizedWidgetPoint = Vector2.zero; RectTransform panelRectTransform = rivePanel.WidgetContainer; if (panelRectTransform == null) { DebugLogger.Instance.LogError("Panel RectTransform is null."); return false; } var panelRect = panelRectTransform.rect; if (panelRect.width <= 0f || panelRect.height <= 0f) { return false; } // We need to turn the normalized point (0..1) into a local point on the panel. // Unity’s RectTransform coordinates are based on the pivot, not always the center. // So when the pivot changes, the rect’s xMin/yMin shift too. If we ignore that, input gets offset. // Using xMin/yMin + (normalized * size) lets us rebuild the original local point again, so everything stays lined up. Vector2 panelLocalPoint = new Vector2( panelRect.xMin + (normalizedPointInPanel.x * panelRect.width), panelRect.yMin + (normalizedPointInPanel.y * panelRect.height) ); Vector3 worldPoint = panelRectTransform.TransformPoint(panelLocalPoint); Vector3 widgetLocalPoint = widget.RectTransform.InverseTransformPoint(worldPoint); normalizedWidgetPoint = new Vector2( (widgetLocalPoint.x - widget.RectTransform.rect.xMin) / widget.RectTransform.rect.width, (widgetLocalPoint.y - widget.RectTransform.rect.yMin) / widget.RectTransform.rect.height ); if (widget.RectTransform.rect.Contains(widgetLocalPoint)) { return true; } return false; } } }