TrackedDeviceRaycaster.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEngine.EventSystems;
  5. using UnityEngine.InputSystem.Utilities;
  6. using UnityEngine.Serialization;
  7. using UnityEngine.UI;
  8. namespace UnityEngine.InputSystem.UI
  9. {
  10. /// <summary>
  11. /// Raycasting implementation for use with <see cref="TrackedDevice"/>s.
  12. /// </summary>
  13. /// <remarks>
  14. /// This component needs to be added alongside the <c>Canvas</c> component. Usually, raycasting is
  15. /// performed by the <c>GraphicRaycaster</c> component found there but for 3D raycasting necessary for
  16. /// tracked devices, this component is required.
  17. /// </remarks>
  18. [AddComponentMenu("Event/Tracked Device Raycaster")]
  19. [RequireComponent(typeof(Canvas))]
  20. public class TrackedDeviceRaycaster : BaseRaycaster
  21. {
  22. private struct RaycastHitData
  23. {
  24. public RaycastHitData(Graphic graphic, Vector3 worldHitPosition, Vector2 screenPosition, float distance)
  25. {
  26. this.graphic = graphic;
  27. this.worldHitPosition = worldHitPosition;
  28. this.screenPosition = screenPosition;
  29. this.distance = distance;
  30. }
  31. public Graphic graphic { get; }
  32. public Vector3 worldHitPosition { get; }
  33. public Vector2 screenPosition { get; }
  34. public float distance { get; }
  35. }
  36. public override Camera eventCamera
  37. {
  38. get
  39. {
  40. var myCanvas = canvas;
  41. return myCanvas != null ? myCanvas.worldCamera : null;
  42. }
  43. }
  44. public LayerMask blockingMask
  45. {
  46. get => m_BlockingMask;
  47. set => m_BlockingMask = value;
  48. }
  49. public bool checkFor3DOcclusion
  50. {
  51. get => m_CheckFor3DOcclusion;
  52. set => m_CheckFor3DOcclusion = value;
  53. }
  54. public bool checkFor2DOcclusion
  55. {
  56. get => m_CheckFor2DOcclusion;
  57. set => m_CheckFor2DOcclusion = value;
  58. }
  59. public bool ignoreReversedGraphics
  60. {
  61. get => m_IgnoreReversedGraphics;
  62. set => m_IgnoreReversedGraphics = value;
  63. }
  64. public float maxDistance
  65. {
  66. get => m_MaxDistance;
  67. set => m_MaxDistance = value;
  68. }
  69. protected override void OnEnable()
  70. {
  71. base.OnEnable();
  72. s_Instances.AppendWithCapacity(this);
  73. }
  74. protected override void OnDisable()
  75. {
  76. var index = s_Instances.IndexOfReference(this);
  77. if (index != -1)
  78. s_Instances.RemoveAtByMovingTailWithCapacity(index);
  79. base.OnDisable();
  80. }
  81. public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
  82. {
  83. if (eventData is ExtendedPointerEventData trackedEventData && trackedEventData.pointerType == UIPointerType.Tracked)
  84. PerformRaycast(trackedEventData, resultAppendList);
  85. }
  86. // Use this list on each raycast to avoid continually allocating.
  87. [NonSerialized]
  88. private List<RaycastHitData> m_RaycastResultsCache = new List<RaycastHitData>();
  89. internal void PerformRaycast(ExtendedPointerEventData eventData, List<RaycastResult> resultAppendList)
  90. {
  91. if (canvas == null)
  92. return;
  93. if (eventCamera == null)
  94. return;
  95. var ray = new Ray(eventData.trackedDevicePosition, eventData.trackedDeviceOrientation * Vector3.forward);
  96. var hitDistance = m_MaxDistance;
  97. #if UNITY_INPUT_SYSTEM_ENABLE_PHYSICS
  98. if (m_CheckFor3DOcclusion)
  99. {
  100. var hits = Physics.RaycastAll(ray, hitDistance, m_BlockingMask);
  101. if (hits.Length > 0 && hits[0].distance < hitDistance)
  102. {
  103. hitDistance = hits[0].distance;
  104. }
  105. }
  106. #endif
  107. #if UNITY_INPUT_SYSTEM_ENABLE_PHYSICS2D
  108. if (m_CheckFor2DOcclusion)
  109. {
  110. var raycastDistance = hitDistance;
  111. var hits = Physics2D.GetRayIntersectionAll(ray, raycastDistance, m_BlockingMask);
  112. if (hits.Length > 0 && hits[0].fraction * raycastDistance < hitDistance)
  113. {
  114. hitDistance = hits[0].fraction * raycastDistance;
  115. }
  116. }
  117. #endif
  118. m_RaycastResultsCache.Clear();
  119. SortedRaycastGraphics(canvas, ray, m_RaycastResultsCache);
  120. // Now that we have a list of sorted hits, process any extra settings and filters.
  121. for (var i = 0; i < m_RaycastResultsCache.Count; i++)
  122. {
  123. var validHit = true;
  124. var hitData = m_RaycastResultsCache[i];
  125. var go = hitData.graphic.gameObject;
  126. if (m_IgnoreReversedGraphics)
  127. {
  128. var forward = ray.direction;
  129. var goDirection = go.transform.rotation * Vector3.forward;
  130. validHit = Vector3.Dot(forward, goDirection) > 0;
  131. }
  132. validHit &= hitData.distance < hitDistance;
  133. if (validHit)
  134. {
  135. var castResult = new RaycastResult
  136. {
  137. gameObject = go,
  138. module = this,
  139. distance = hitData.distance,
  140. index = resultAppendList.Count,
  141. depth = hitData.graphic.depth,
  142. worldPosition = hitData.worldHitPosition,
  143. screenPosition = hitData.screenPosition,
  144. };
  145. resultAppendList.Add(castResult);
  146. }
  147. }
  148. }
  149. internal static InlinedArray<TrackedDeviceRaycaster> s_Instances;
  150. static readonly List<RaycastHitData> s_SortedGraphics = new List<RaycastHitData>();
  151. private void SortedRaycastGraphics(Canvas canvas, Ray ray, List<RaycastHitData> results)
  152. {
  153. var graphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
  154. s_SortedGraphics.Clear();
  155. for (var i = 0; i < graphics.Count; ++i)
  156. {
  157. var graphic = graphics[i];
  158. if (graphic.depth == -1)
  159. continue;
  160. Vector3 worldPos;
  161. float distance;
  162. if (RayIntersectsRectTransform(graphic.rectTransform, ray, out worldPos, out distance))
  163. {
  164. Vector2 screenPos = eventCamera.WorldToScreenPoint(worldPos);
  165. // mask/image intersection - See Unity docs on eventAlphaThreshold for when this does anything
  166. if (graphic.Raycast(screenPos, eventCamera))
  167. {
  168. s_SortedGraphics.Add(new RaycastHitData(graphic, worldPos, screenPos, distance));
  169. }
  170. }
  171. }
  172. s_SortedGraphics.Sort((g1, g2) => g2.graphic.depth.CompareTo(g1.graphic.depth));
  173. results.AddRange(s_SortedGraphics);
  174. }
  175. private static bool RayIntersectsRectTransform(RectTransform transform, Ray ray, out Vector3 worldPosition, out float distance)
  176. {
  177. var corners = new Vector3[4];
  178. transform.GetWorldCorners(corners);
  179. var plane = new Plane(corners[0], corners[1], corners[2]);
  180. float enter;
  181. if (plane.Raycast(ray, out enter))
  182. {
  183. var intersection = ray.GetPoint(enter);
  184. var bottomEdge = corners[3] - corners[0];
  185. var leftEdge = corners[1] - corners[0];
  186. var bottomDot = Vector3.Dot(intersection - corners[0], bottomEdge);
  187. var leftDot = Vector3.Dot(intersection - corners[0], leftEdge);
  188. // If the intersection is right of the left edge and above the bottom edge.
  189. if (leftDot >= 0 && bottomDot >= 0)
  190. {
  191. var topEdge = corners[1] - corners[2];
  192. var rightEdge = corners[3] - corners[2];
  193. var topDot = Vector3.Dot(intersection - corners[2], topEdge);
  194. var rightDot = Vector3.Dot(intersection - corners[2], rightEdge);
  195. //If the intersection is left of the right edge, and below the top edge
  196. if (topDot >= 0 && rightDot >= 0)
  197. {
  198. worldPosition = intersection;
  199. distance = enter;
  200. return true;
  201. }
  202. }
  203. }
  204. worldPosition = Vector3.zero;
  205. distance = 0;
  206. return false;
  207. }
  208. [FormerlySerializedAs("ignoreReversedGraphics")]
  209. [SerializeField]
  210. private bool m_IgnoreReversedGraphics;
  211. [FormerlySerializedAs("checkFor2DOcclusion")]
  212. [SerializeField]
  213. private bool m_CheckFor2DOcclusion;
  214. [FormerlySerializedAs("checkFor3DOcclusion")]
  215. [SerializeField]
  216. private bool m_CheckFor3DOcclusion;
  217. [Tooltip("Maximum distance (in 3D world space) that rays are traced to find a hit.")]
  218. [SerializeField] private float m_MaxDistance = 1000;
  219. [SerializeField]
  220. private LayerMask m_BlockingMask;
  221. [NonSerialized]
  222. private Canvas m_Canvas;
  223. private Canvas canvas
  224. {
  225. get
  226. {
  227. if (m_Canvas != null)
  228. return m_Canvas;
  229. m_Canvas = GetComponent<Canvas>();
  230. return m_Canvas;
  231. }
  232. }
  233. }
  234. }
  235. #endif