GraphicRaycaster.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using UnityEngine.EventSystems;
  5. using UnityEngine.Serialization;
  6. namespace UnityEngine.UI
  7. {
  8. [AddComponentMenu("Event/Graphic Raycaster")]
  9. [RequireComponent(typeof(Canvas))]
  10. /// <summary>
  11. /// A derived BaseRaycaster to raycast against Graphic elements.
  12. /// </summary>
  13. public class GraphicRaycaster : BaseRaycaster
  14. {
  15. protected const int kNoEventMaskSet = -1;
  16. /// <summary>
  17. /// Type of raycasters to check against to check for canvas blocking elements.
  18. /// </summary>
  19. public enum BlockingObjects
  20. {
  21. /// <summary>
  22. /// Perform no raycasts.
  23. /// </summary>
  24. None = 0,
  25. /// <summary>
  26. /// Perform a 2D raycast check to check for blocking 2D elements
  27. /// </summary>
  28. TwoD = 1,
  29. /// <summary>
  30. /// Perform a 3D raycast check to check for blocking 3D elements
  31. /// </summary>
  32. ThreeD = 2,
  33. /// <summary>
  34. /// Perform a 2D and a 3D raycasts to check for blocking 2D and 3D elements.
  35. /// </summary>
  36. All = 3,
  37. }
  38. /// <summary>
  39. /// Priority of the raycaster based upon sort order.
  40. /// </summary>
  41. /// <returns>
  42. /// The sortOrder priority.
  43. /// </returns>
  44. public override int sortOrderPriority
  45. {
  46. get
  47. {
  48. // We need to return the sorting order here as distance will all be 0 for overlay.
  49. if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
  50. return canvas.sortingOrder;
  51. return base.sortOrderPriority;
  52. }
  53. }
  54. /// <summary>
  55. /// Priority of the raycaster based upon render order.
  56. /// </summary>
  57. /// <returns>
  58. /// The renderOrder priority.
  59. /// </returns>
  60. public override int renderOrderPriority
  61. {
  62. get
  63. {
  64. // We need to return the sorting order here as distance will all be 0 for overlay.
  65. if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
  66. return canvas.rootCanvas.renderOrder;
  67. return base.renderOrderPriority;
  68. }
  69. }
  70. [FormerlySerializedAs("ignoreReversedGraphics")]
  71. [SerializeField]
  72. private bool m_IgnoreReversedGraphics = true;
  73. [FormerlySerializedAs("blockingObjects")]
  74. [SerializeField]
  75. private BlockingObjects m_BlockingObjects = BlockingObjects.None;
  76. /// <summary>
  77. /// Whether Graphics facing away from the raycaster are checked for raycasts.
  78. /// </summary>
  79. public bool ignoreReversedGraphics { get {return m_IgnoreReversedGraphics; } set { m_IgnoreReversedGraphics = value; } }
  80. /// <summary>
  81. /// The type of objects that are checked to determine if they block graphic raycasts.
  82. /// </summary>
  83. public BlockingObjects blockingObjects { get {return m_BlockingObjects; } set { m_BlockingObjects = value; } }
  84. [SerializeField]
  85. protected LayerMask m_BlockingMask = kNoEventMaskSet;
  86. /// <summary>
  87. /// The type of objects specified through LayerMask that are checked to determine if they block graphic raycasts.
  88. /// </summary>
  89. public LayerMask blockingMask { get { return m_BlockingMask; } set { m_BlockingMask = value; } }
  90. private Canvas m_Canvas;
  91. protected GraphicRaycaster()
  92. {}
  93. private Canvas canvas
  94. {
  95. get
  96. {
  97. if (m_Canvas != null)
  98. return m_Canvas;
  99. m_Canvas = GetComponent<Canvas>();
  100. return m_Canvas;
  101. }
  102. }
  103. [NonSerialized] private List<Graphic> m_RaycastResults = new List<Graphic>();
  104. /// <summary>
  105. /// Perform the raycast against the list of graphics associated with the Canvas.
  106. /// </summary>
  107. /// <param name="eventData">Current event data</param>
  108. /// <param name="resultAppendList">List of hit objects to append new results to.</param>
  109. public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
  110. {
  111. if (canvas == null)
  112. return;
  113. var canvasGraphics = GraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);
  114. if (canvasGraphics == null || canvasGraphics.Count == 0)
  115. return;
  116. int displayIndex;
  117. var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference
  118. if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
  119. displayIndex = canvas.targetDisplay;
  120. else
  121. displayIndex = currentEventCamera.targetDisplay;
  122. var eventPosition = Display.RelativeMouseAt(eventData.position);
  123. if (eventPosition != Vector3.zero)
  124. {
  125. // We support multiple display and display identification based on event position.
  126. int eventDisplayIndex = (int)eventPosition.z;
  127. // Discard events that are not part of this display so the user does not interact with multiple displays at once.
  128. if (eventDisplayIndex != displayIndex)
  129. return;
  130. }
  131. else
  132. {
  133. // The multiple display system is not supported on all platforms, when it is not supported the returned position
  134. // will be all zeros so when the returned index is 0 we will default to the event data to be safe.
  135. eventPosition = eventData.position;
  136. // We dont really know in which display the event occured. We will process the event assuming it occured in our display.
  137. }
  138. // Convert to view space
  139. Vector2 pos;
  140. if (currentEventCamera == null)
  141. {
  142. // Multiple display support only when not the main display. For display 0 the reported
  143. // resolution is always the desktops resolution since its part of the display API,
  144. // so we use the standard none multiple display method. (case 741751)
  145. float w = Screen.width;
  146. float h = Screen.height;
  147. if (displayIndex > 0 && displayIndex < Display.displays.Length)
  148. {
  149. w = Display.displays[displayIndex].systemWidth;
  150. h = Display.displays[displayIndex].systemHeight;
  151. }
  152. pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
  153. }
  154. else
  155. pos = currentEventCamera.ScreenToViewportPoint(eventPosition);
  156. // If it's outside the camera's viewport, do nothing
  157. if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
  158. return;
  159. float hitDistance = float.MaxValue;
  160. Ray ray = new Ray();
  161. if (currentEventCamera != null)
  162. ray = currentEventCamera.ScreenPointToRay(eventPosition);
  163. if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
  164. {
  165. float distanceToClipPlane = 100.0f;
  166. if (currentEventCamera != null)
  167. {
  168. float projectionDirection = ray.direction.z;
  169. distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection)
  170. ? Mathf.Infinity
  171. : Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection);
  172. }
  173. #if PACKAGE_PHYSICS
  174. if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All)
  175. {
  176. if (ReflectionMethodsCache.Singleton.raycast3D != null)
  177. {
  178. var hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, (int)m_BlockingMask);
  179. if (hits.Length > 0)
  180. hitDistance = hits[0].distance;
  181. }
  182. }
  183. #endif
  184. #if PACKAGE_PHYSICS2D
  185. if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All)
  186. {
  187. if (ReflectionMethodsCache.Singleton.raycast2D != null)
  188. {
  189. var hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, (int)m_BlockingMask);
  190. if (hits.Length > 0)
  191. hitDistance = hits[0].distance;
  192. }
  193. }
  194. #endif
  195. }
  196. m_RaycastResults.Clear();
  197. Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
  198. int totalCount = m_RaycastResults.Count;
  199. for (var index = 0; index < totalCount; index++)
  200. {
  201. var go = m_RaycastResults[index].gameObject;
  202. bool appendGraphic = true;
  203. if (ignoreReversedGraphics)
  204. {
  205. if (currentEventCamera == null)
  206. {
  207. // If we dont have a camera we know that we should always be facing forward
  208. var dir = go.transform.rotation * Vector3.forward;
  209. appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
  210. }
  211. else
  212. {
  213. // If we have a camera compare the direction against the cameras forward.
  214. var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane;
  215. appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0;
  216. }
  217. }
  218. if (appendGraphic)
  219. {
  220. float distance = 0;
  221. Transform trans = go.transform;
  222. Vector3 transForward = trans.forward;
  223. if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
  224. distance = 0;
  225. else
  226. {
  227. // http://geomalgorithms.com/a06-_intersect-2.html
  228. distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));
  229. // Check to see if the go is behind the camera.
  230. if (distance < 0)
  231. continue;
  232. }
  233. if (distance >= hitDistance)
  234. continue;
  235. var castResult = new RaycastResult
  236. {
  237. gameObject = go,
  238. module = this,
  239. distance = distance,
  240. screenPosition = eventPosition,
  241. displayIndex = displayIndex,
  242. index = resultAppendList.Count,
  243. depth = m_RaycastResults[index].depth,
  244. sortingLayer = canvas.sortingLayerID,
  245. sortingOrder = canvas.sortingOrder,
  246. worldPosition = ray.origin + ray.direction * distance,
  247. worldNormal = -transForward
  248. };
  249. resultAppendList.Add(castResult);
  250. }
  251. }
  252. }
  253. /// <summary>
  254. /// The camera that will generate rays for this raycaster.
  255. /// </summary>
  256. /// <returns>
  257. /// - Null if Camera mode is ScreenSpaceOverlay or ScreenSpaceCamera and has no camera.
  258. /// - canvas.worldCanvas if not null
  259. /// - Camera.main.
  260. /// </returns>
  261. public override Camera eventCamera
  262. {
  263. get
  264. {
  265. var canvas = this.canvas;
  266. var renderMode = canvas.renderMode;
  267. if (renderMode == RenderMode.ScreenSpaceOverlay
  268. || (renderMode == RenderMode.ScreenSpaceCamera && canvas.worldCamera == null))
  269. return null;
  270. return canvas.worldCamera ?? Camera.main;
  271. }
  272. }
  273. /// <summary>
  274. /// Perform a raycast into the screen and collect all graphics underneath it.
  275. /// </summary>
  276. [NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
  277. private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
  278. {
  279. // Necessary for the event system
  280. int totalCount = foundGraphics.Count;
  281. for (int i = 0; i < totalCount; ++i)
  282. {
  283. Graphic graphic = foundGraphics[i];
  284. // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
  285. if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)
  286. continue;
  287. if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera, graphic.raycastPadding))
  288. continue;
  289. if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
  290. continue;
  291. if (graphic.Raycast(pointerPosition, eventCamera))
  292. {
  293. s_SortedGraphics.Add(graphic);
  294. }
  295. }
  296. s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
  297. totalCount = s_SortedGraphics.Count;
  298. for (int i = 0; i < totalCount; ++i)
  299. results.Add(s_SortedGraphics[i]);
  300. s_SortedGraphics.Clear();
  301. }
  302. }
  303. }