123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- #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
- {
- /// <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>
- [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<RaycastResult> resultAppendList)
- {
- if (eventData is ExtendedPointerEventData trackedEventData && trackedEventData.pointerType == UIPointerType.Tracked)
- PerformRaycast(trackedEventData, resultAppendList);
- }
-
- // Cached instances for raycasts hits to minimize GC.
- [NonSerialized] 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 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<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;
- }
-
- [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<Canvas>();
- return m_Canvas;
- }
- }
- }
- }
- #endif
|