#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI using System; using System.Collections.Generic; using UnityEngine.EventSystems; using UnityEngine.InputSystem.Utilities; using UnityEngine.Serialization; using UnityEngine.UI; namespace UnityEngine.InputSystem.UI { /// /// Raycasting implementation for use with s. /// /// /// This component needs to be added alongside the Canvas component. Usually, raycasting is /// performed by the GraphicRaycaster component found there but for 3D raycasting necessary for /// tracked devices, this component is required. /// [AddComponentMenu("Event/Tracked Device Raycaster")] [RequireComponent(typeof(Canvas))] public class TrackedDeviceRaycaster : BaseRaycaster { private struct RaycastHitData { public RaycastHitData(Graphic graphic, Vector3 worldHitPosition, Vector2 screenPosition, float distance) { this.graphic = graphic; this.worldHitPosition = worldHitPosition; this.screenPosition = screenPosition; this.distance = distance; } public Graphic graphic { get; } public Vector3 worldHitPosition { get; } public Vector2 screenPosition { get; } public float distance { get; } } public override Camera eventCamera { get { var myCanvas = canvas; return myCanvas != null ? myCanvas.worldCamera : null; } } public LayerMask blockingMask { get => m_BlockingMask; set => m_BlockingMask = value; } public bool checkFor3DOcclusion { get => m_CheckFor3DOcclusion; set => m_CheckFor3DOcclusion = value; } public bool checkFor2DOcclusion { get => m_CheckFor2DOcclusion; set => m_CheckFor2DOcclusion = value; } public bool ignoreReversedGraphics { get => m_IgnoreReversedGraphics; set => m_IgnoreReversedGraphics = value; } public float maxDistance { get => m_MaxDistance; set => m_MaxDistance = value; } protected override void OnEnable() { base.OnEnable(); s_Instances.AppendWithCapacity(this); } protected override void OnDisable() { var index = s_Instances.IndexOfReference(this); if (index != -1) s_Instances.RemoveAtByMovingTailWithCapacity(index); base.OnDisable(); } public override void Raycast(PointerEventData eventData, List resultAppendList) { if (eventData is ExtendedPointerEventData trackedEventData && trackedEventData.pointerType == UIPointerType.Tracked) PerformRaycast(trackedEventData, resultAppendList); } // Cached instances for raycasts hits to minimize GC. [NonSerialized] private List m_RaycastResultsCache = new List(); internal void PerformRaycast(ExtendedPointerEventData eventData, List resultAppendList) { if (canvas == null) return; if (eventCamera == null) return; var ray = new Ray(eventData.trackedDevicePosition, eventData.trackedDeviceOrientation * Vector3.forward); var hitDistance = m_MaxDistance; #if UNITY_INPUT_SYSTEM_ENABLE_PHYSICS if (m_CheckFor3DOcclusion) { if (Physics.Raycast(ray, out var hit, maxDistance: hitDistance, layerMask: m_BlockingMask)) hitDistance = hit.distance; } #endif #if UNITY_INPUT_SYSTEM_ENABLE_PHYSICS2D if (m_CheckFor2DOcclusion) { var raycastDistance = hitDistance; var hits = Physics2D.GetRayIntersection(ray, raycastDistance, m_BlockingMask); if (hits.collider != null) hitDistance = hits.distance; } #endif m_RaycastResultsCache.Clear(); SortedRaycastGraphics(canvas, ray, m_RaycastResultsCache); // Now that we have a list of sorted hits, process any extra settings and filters. for (var i = 0; i < m_RaycastResultsCache.Count; i++) { var validHit = true; var hitData = m_RaycastResultsCache[i]; var go = hitData.graphic.gameObject; if (m_IgnoreReversedGraphics) { var forward = ray.direction; var goDirection = go.transform.rotation * Vector3.forward; validHit = Vector3.Dot(forward, goDirection) > 0; } validHit &= hitData.distance < hitDistance; if (validHit) { var castResult = new RaycastResult { gameObject = go, module = this, distance = hitData.distance, index = resultAppendList.Count, depth = hitData.graphic.depth, worldPosition = hitData.worldHitPosition, screenPosition = hitData.screenPosition, }; resultAppendList.Add(castResult); } } } internal static InlinedArray s_Instances; private static readonly List s_SortedGraphics = new List(); private void SortedRaycastGraphics(Canvas canvas, Ray ray, List results) { var graphics = GraphicRegistry.GetGraphicsForCanvas(canvas); s_SortedGraphics.Clear(); for (var i = 0; i < graphics.Count; ++i) { var graphic = graphics[i]; if (graphic.depth == -1) continue; Vector3 worldPos; float distance; if (RayIntersectsRectTransform(graphic.rectTransform, ray, out worldPos, out distance)) { Vector2 screenPos = eventCamera.WorldToScreenPoint(worldPos); // mask/image intersection - See Unity docs on eventAlphaThreshold for when this does anything if (graphic.Raycast(screenPos, eventCamera)) { s_SortedGraphics.Add(new RaycastHitData(graphic, worldPos, screenPos, distance)); } } } s_SortedGraphics.Sort((g1, g2) => g2.graphic.depth.CompareTo(g1.graphic.depth)); results.AddRange(s_SortedGraphics); } private static bool RayIntersectsRectTransform(RectTransform transform, Ray ray, out Vector3 worldPosition, out float distance) { var corners = new Vector3[4]; transform.GetWorldCorners(corners); var plane = new Plane(corners[0], corners[1], corners[2]); float enter; if (plane.Raycast(ray, out enter)) { var intersection = ray.GetPoint(enter); var bottomEdge = corners[3] - corners[0]; var leftEdge = corners[1] - corners[0]; var bottomDot = Vector3.Dot(intersection - corners[0], bottomEdge); var leftDot = Vector3.Dot(intersection - corners[0], leftEdge); // If the intersection is right of the left edge and above the bottom edge. if (leftDot >= 0 && bottomDot >= 0) { var topEdge = corners[1] - corners[2]; var rightEdge = corners[3] - corners[2]; var topDot = Vector3.Dot(intersection - corners[2], topEdge); var rightDot = Vector3.Dot(intersection - corners[2], rightEdge); //If the intersection is left of the right edge, and below the top edge if (topDot >= 0 && rightDot >= 0) { worldPosition = intersection; distance = enter; return true; } } } worldPosition = Vector3.zero; distance = 0; return false; } [FormerlySerializedAs("ignoreReversedGraphics")] [SerializeField] private bool m_IgnoreReversedGraphics; [FormerlySerializedAs("checkFor2DOcclusion")] [SerializeField] private bool m_CheckFor2DOcclusion; [FormerlySerializedAs("checkFor3DOcclusion")] [SerializeField] private bool m_CheckFor3DOcclusion; [Tooltip("Maximum distance (in 3D world space) that rays are traced to find a hit.")] [SerializeField] private float m_MaxDistance = 1000; [SerializeField] private LayerMask m_BlockingMask; [NonSerialized] private Canvas m_Canvas; private Canvas canvas { get { if (m_Canvas != null) return m_Canvas; m_Canvas = GetComponent(); return m_Canvas; } } } } #endif