Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Plugins /UI /TrackedDeviceRaycaster.cs
| using System; | |
| using System.Collections.Generic; | |
| using UnityEngine.EventSystems; | |
| using UnityEngine.InputSystem.Utilities; | |
| using UnityEngine.Serialization; | |
| using UnityEngine.UI; | |
| namespace UnityEngine.InputSystem.UI | |
| { | |
| /// <summary> | |
| /// Raycasting implementation for use with <see cref="TrackedDevice"/>s. | |
| /// </summary> | |
| /// <remarks> | |
| /// This component needs to be added alongside the <c>Canvas</c> component. Usually, raycasting is | |
| /// performed by the <c>GraphicRaycaster</c> component found there but for 3D raycasting necessary for | |
| /// tracked devices, this component is required. | |
| /// </remarks> | |
| [] | |
| [] | |
| 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<RaycastResult> resultAppendList) | |
| { | |
| if (eventData is ExtendedPointerEventData trackedEventData && trackedEventData.pointerType == UIPointerType.Tracked) | |
| PerformRaycast(trackedEventData, resultAppendList); | |
| } | |
| // Cached instances for raycasts hits to minimize GC. | |
| [] private List<RaycastHitData> m_RaycastResultsCache = new List<RaycastHitData>(); | |
| internal void PerformRaycast(ExtendedPointerEventData eventData, List<RaycastResult> resultAppendList) | |
| { | |
| if (canvas == null) | |
| return; | |
| if (eventCamera == null) | |
| return; | |
| var ray = new Ray(eventData.trackedDevicePosition, eventData.trackedDeviceOrientation * Vector3.forward); | |
| var hitDistance = m_MaxDistance; | |
| if (m_CheckFor3DOcclusion) | |
| { | |
| if (Physics.Raycast(ray, out var hit, maxDistance: hitDistance, layerMask: m_BlockingMask)) | |
| hitDistance = hit.distance; | |
| } | |
| if (m_CheckFor2DOcclusion) | |
| { | |
| var raycastDistance = hitDistance; | |
| var hits = Physics2D.GetRayIntersection(ray, raycastDistance, m_BlockingMask); | |
| if (hits.collider != null) | |
| hitDistance = hits.distance; | |
| } | |
| 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<TrackedDeviceRaycaster> s_Instances; | |
| private static readonly List<RaycastHitData> s_SortedGraphics = new List<RaycastHitData>(); | |
| private void SortedRaycastGraphics(Canvas canvas, Ray ray, List<RaycastHitData> 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; | |
| } | |
| [] | |
| [] | |
| private bool m_IgnoreReversedGraphics; | |
| [] | |
| [] | |
| private bool m_CheckFor2DOcclusion; | |
| [] | |
| [] | |
| private bool m_CheckFor3DOcclusion; | |
| [] | |
| [] private float m_MaxDistance = 1000; | |
| [] | |
| private LayerMask m_BlockingMask; | |
| [] | |
| private Canvas m_Canvas; | |
| private Canvas canvas | |
| { | |
| get | |
| { | |
| if (m_Canvas != null) | |
| return m_Canvas; | |
| m_Canvas = GetComponent<Canvas>(); | |
| return m_Canvas; | |
| } | |
| } | |
| } | |
| } | |