CurvedUIRaycaster.cs 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using UnityEngine.EventSystems;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System;
  7. namespace CurvedUI
  8. {
  9. #if CURVEDUI_GOOGLEVR
  10. public class CurvedUIRaycaster : GvrPointerGraphicRaycaster
  11. #else
  12. public class CurvedUIRaycaster : GraphicRaycaster
  13. #endif
  14. {
  15. [SerializeField]
  16. bool showDebug = false;
  17. //Settings--------------------------------------//
  18. // CurvedUIRaycaster must modify the position of the eventData to make it valid for the curved canvas.
  19. // It can either create a copy, or override the original. The copy will only be used for this canvas, in this frame.
  20. // The overridden original will be carried to other canvases and next frames.
  21. //
  22. // Set this to TRUE if this raycaster should override the original eventData.
  23. // Overriding eventData allows canvas to use 1:1 scrolling. Scroll rects and sliders behave as they should on a curved surface and follow the pointer.
  24. // This however breaks the interactions with flat canvases in the same scene as original eventData will not be correct for them any more.
  25. //
  26. // Setting this to FALSE will create a copy of the eventData for each canvas.
  27. // Flat canvases on the same scene will work fine, but scroll rects on curved canvases will move faster / slower than the pointer.
  28. // May break dragging and scrolling as there will be no past eventdata to calculate delta position from.
  29. //
  30. // default true.
  31. bool overrideEventData = true;
  32. //Variables --------------------------------------//
  33. Canvas myCanvas;
  34. CurvedUISettings mySettings;
  35. Vector3 cyllinderMidPoint;
  36. List<GameObject> objectsUnderPointer = new List<GameObject>();
  37. Vector2 lastCanvasPos = Vector2.zero;
  38. GameObject colliderContainer;
  39. PointerEventData lastFrameEventData;
  40. PointerEventData curEventData;
  41. PointerEventData eventDataToUse;
  42. Ray cachedRay;
  43. Graphic gph;
  44. //gaze click
  45. List<GameObject> selectablesUnderGaze = new List<GameObject>();
  46. List<GameObject> selectablesUnderGazeLastFrame = new List<GameObject>();
  47. float objectsUnderGazeLastChangeTime;
  48. bool gazeClickExecuted = false;
  49. bool pointingAtCanvas = false;
  50. bool pointingAtCanvasLastFrame = false;
  51. #region LIFECYCLE
  52. protected override void Awake()
  53. {
  54. base.Awake();
  55. mySettings = GetComponentInParent<CurvedUISettings>();
  56. if (mySettings == null) return;
  57. myCanvas = mySettings.GetComponent<Canvas>();
  58. cyllinderMidPoint = new Vector3(0, 0, -mySettings.GetCyllinderRadiusInCanvasSpace());
  59. //this must be set to false to make sure proper interactions.
  60. //Otherwise, Unity may ignore Selectables on edges of heavily curved canvas.
  61. ignoreReversedGraphics = false;
  62. }
  63. protected override void Start()
  64. {
  65. if (mySettings == null) return;
  66. CheckEventCamera();
  67. CreateCollider();
  68. #if CURVEDUI_GOOGLEVR
  69. //Find if there is a GvrPointerPhysicsRaycaster on the scene that can override our Raycasts.
  70. if (Camera.main != null && Camera.main.GetComponent<GvrPointerPhysicsRaycaster>() != null)
  71. {
  72. LayerMask mask = Camera.main.GetComponent<GvrPointerPhysicsRaycaster>().eventMask;
  73. if (IsInLayerMask(this.gameObject.layer, mask)){
  74. Debug.LogWarning("CURVEDUI: GvrPointerPhysicsRaycaster is raycasting over this canvas' layer (" +this.gameObject.name +" - " + LayerMask.LayerToName(this.gameObject.layer)+" layer). "
  75. + "This can make the UI unusable. It has been automatically fixed for this run, but your 3D objects may now be unusable. "
  76. + "Make sure your GvrPointerPhysicsRaycaster is not raycasting on this object's layer UI by editing its properties. Click here to highlight it.", Camera.main.gameObject);
  77. mask = mask & ~(1 << this.gameObject.layer);
  78. Camera.main.GetComponent<GvrPointerPhysicsRaycaster>().eventMask = mask;
  79. }
  80. }
  81. #endif
  82. }
  83. protected virtual void Update()
  84. {
  85. if (mySettings == null) return;
  86. //Gaze click process.
  87. if (CurvedUIInputModule.ControlMethod == CurvedUIInputModule.CUIControlMethod.GAZE && CurvedUIInputModule.Instance.GazeUseTimedClick)
  88. {
  89. if (pointingAtCanvas)
  90. {
  91. //first frame gaze enters canvas. Make sure we dont click immidiately upon entering canvas
  92. if (!pointingAtCanvasLastFrame)
  93. ResetGazeTimedClick();
  94. ProcessGazeTimedClick();
  95. //save current selectablesUnderGaze
  96. selectablesUnderGazeLastFrame.Clear();
  97. selectablesUnderGazeLastFrame.AddRange(selectablesUnderGaze);
  98. //find selectables we're currently pointing at in objects under pointer
  99. selectablesUnderGaze.Clear();
  100. selectablesUnderGaze.AddRange(objectsUnderPointer);
  101. selectablesUnderGaze.RemoveAll(obj =>
  102. obj.GetComponent<Selectable>() == null || obj.GetComponent<Selectable>().interactable == false);
  103. //Animate progress bar
  104. if (GazeProgressImage)
  105. {
  106. if (GazeProgressImage.type != Image.Type.Filled) GazeProgressImage.type = Image.Type.Filled;
  107. GazeProgressImage.fillAmount =
  108. (Time.time - objectsUnderGazeLastChangeTime).RemapAndClamp(CurvedUIInputModule.Instance.GazeClickTimerDelay, CurvedUIInputModule.Instance.GazeClickTimer + CurvedUIInputModule.Instance.GazeClickTimerDelay, 0, 1);
  109. }
  110. }
  111. else if (!pointingAtCanvas && pointingAtCanvasLastFrame) //first frame after gaze pointer leaves this canvas.
  112. {
  113. //not poiting at canvas, reset the timer.
  114. ResetGazeTimedClick();
  115. if (GazeProgressImage) GazeProgressImage.fillAmount = 0;
  116. }
  117. }
  118. pointingAtCanvasLastFrame = pointingAtCanvas;
  119. //reset this variable. It will be checked again during next Raycast method run.
  120. pointingAtCanvas = false;
  121. }
  122. #endregion
  123. #region GAZE INTERACTION
  124. void ProcessGazeTimedClick()
  125. {
  126. //debug
  127. //string str = " Object under pointer: ";
  128. //foreach (GameObject go in objectsUnderPointer) str += go.name + ", ";
  129. //Debug.Log(str);
  130. //two lists are not the same - selected objects changed
  131. if (selectablesUnderGazeLastFrame.Count == 0 || selectablesUnderGazeLastFrame.Count != selectablesUnderGaze.Count)
  132. {
  133. ResetGazeTimedClick();
  134. return;
  135. }
  136. //Check if objects changed since last frame
  137. for (int i = 0; i < selectablesUnderGazeLastFrame.Count && i < selectablesUnderGaze.Count; i++)
  138. {
  139. if (selectablesUnderGazeLastFrame[i].GetInstanceID() != selectablesUnderGaze[i].GetInstanceID())
  140. {
  141. ResetGazeTimedClick();
  142. return;
  143. }
  144. }
  145. //Check if time is done and we havent executed the click yet
  146. if (!gazeClickExecuted && Time.time > objectsUnderGazeLastChangeTime + CurvedUIInputModule.Instance.GazeClickTimer + CurvedUIInputModule.Instance.GazeClickTimerDelay)
  147. {
  148. Click();
  149. gazeClickExecuted = true;
  150. }
  151. }
  152. void ResetGazeTimedClick()
  153. {
  154. objectsUnderGazeLastChangeTime = Time.time;
  155. gazeClickExecuted = false;
  156. }
  157. #endregion
  158. #region PHYSICS RAYCASTING
  159. Vector3 mousePressPosition, mousePosition;
  160. public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
  161. {
  162. if (mySettings == null)
  163. {
  164. base.Raycast(eventData, resultAppendList);
  165. return;
  166. }
  167. if (!mySettings.Interactable)
  168. return;
  169. //check if we have a world camera to process events by
  170. if (!CheckEventCamera())
  171. {
  172. Debug.LogWarning("CurvedUI: No WORLD CAMERA assigned to Canvas " + this.gameObject.name + " to use for event processing!", myCanvas.gameObject);
  173. return;
  174. }
  175. mousePressPosition = eventData.pressPosition;
  176. mousePosition = eventData.position;
  177. //get a ray to raycast with depending on the control method
  178. cachedRay = CurvedUIInputModule.Instance.GetEventRay(myCanvas.worldCamera);
  179. //special case for GAZE and WORLD MOUSE
  180. if (CurvedUIInputModule.ControlMethod == CurvedUIInputModule.CUIControlMethod.GAZE)
  181. UpdateSelectedObjects(eventData);
  182. else if (CurvedUIInputModule.ControlMethod == CurvedUIInputModule.CUIControlMethod.WORLD_MOUSE)
  183. cachedRay = new Ray(myCanvas.worldCamera.transform.position, (mySettings.CanvasToCurvedCanvas(CurvedUIInputModule.Instance.WorldSpaceMouseInCanvasSpace) - myCanvas.worldCamera.transform.position));
  184. //Create a copy of the eventData to be used by this canvas.
  185. if (curEventData == null)
  186. curEventData = new PointerEventData(EventSystem.current);
  187. if (!overrideEventData)
  188. {
  189. curEventData.pointerEnter = eventData.pointerEnter;
  190. curEventData.rawPointerPress = eventData.rawPointerPress;
  191. curEventData.pointerDrag = eventData.pointerDrag;
  192. curEventData.pointerCurrentRaycast = eventData.pointerCurrentRaycast;
  193. curEventData.pointerPressRaycast = eventData.pointerPressRaycast;
  194. curEventData.hovered.Clear();
  195. curEventData.hovered.AddRange(eventData.hovered);
  196. curEventData.eligibleForClick = eventData.eligibleForClick;
  197. curEventData.pointerId = eventData.pointerId;
  198. curEventData.position = eventData.position;
  199. curEventData.delta = eventData.delta;
  200. curEventData.pressPosition = eventData.pressPosition;
  201. curEventData.clickTime = eventData.clickTime;
  202. curEventData.clickCount = eventData.clickCount;
  203. curEventData.scrollDelta = eventData.scrollDelta;
  204. curEventData.useDragThreshold = eventData.useDragThreshold;
  205. curEventData.dragging = eventData.dragging;
  206. curEventData.button = eventData.button;
  207. }
  208. if (mySettings.Angle != 0 && mySettings.enabled)
  209. { // use custom raycasting only if Curved effect is enabled
  210. //Getting remappedPosition on the curved canvas ------------------------------//
  211. //This will be later passed to GraphicRaycaster so it can discover interactions as usual.
  212. //If we did not hit the curved canvas, return - no interactions are possible
  213. //Physical raycast to find interaction point
  214. Vector2 remappedPosition = eventData.position;
  215. switch (mySettings.Shape)
  216. {
  217. case CurvedUISettings.CurvedUIShape.CYLINDER:
  218. {
  219. if (!RaycastToCyllinderCanvas(cachedRay, out remappedPosition, false)) return;
  220. break;
  221. }
  222. case CurvedUISettings.CurvedUIShape.CYLINDER_VERTICAL:
  223. {
  224. if (!RaycastToCyllinderVerticalCanvas(cachedRay, out remappedPosition, false)) return;
  225. break;
  226. }
  227. case CurvedUISettings.CurvedUIShape.RING:
  228. {
  229. if (!RaycastToRingCanvas(cachedRay, out remappedPosition, false)) return;
  230. break;
  231. }
  232. case CurvedUISettings.CurvedUIShape.SPHERE:
  233. {
  234. if (!RaycastToSphereCanvas(cachedRay, out remappedPosition, false)) return;
  235. break;
  236. }
  237. }
  238. //if we got here, it means user is pointing at this canvas.
  239. pointingAtCanvas = true;
  240. //Creating eventData for canvas Raycasting -------------------//
  241. //Which eventData were going to use?
  242. eventDataToUse = overrideEventData ? eventData : curEventData;
  243. // Swap event data pressPosition to our remapped pos if this is the frame of the press
  244. if (eventDataToUse.pressPosition == eventDataToUse.position)
  245. eventDataToUse.pressPosition = remappedPosition;
  246. // Swap event data position to our remapped pos
  247. eventDataToUse.position = remappedPosition;
  248. //Scroll Handling---------------------------------------------//
  249. //We must handle scroll a little differently on these platforms
  250. if (CurvedUIInputModule.ControlMethod == CurvedUIInputModule.CUIControlMethod.STEAMVR_LEGACY)
  251. {
  252. eventDataToUse.delta = remappedPosition - lastCanvasPos;
  253. lastCanvasPos = remappedPosition;
  254. }
  255. }
  256. //store objects under pointer so they can quickly retrieved if needed by other scripts
  257. objectsUnderPointer = eventData.hovered;
  258. lastFrameEventData = eventData;
  259. // Use base class raycast method to finish the raycast if we hit anything
  260. FlatRaycast(overrideEventData ? eventData : curEventData, resultAppendList);
  261. eventDataToUse.pressPosition = mousePressPosition;
  262. eventDataToUse.position = mousePosition;
  263. }
  264. RaycastHit hit = new RaycastHit();
  265. public virtual bool RaycastToCyllinderCanvas(Ray ray3D, out Vector2 o_canvasPos, bool OutputInCanvasSpace = false)
  266. {
  267. if (showDebug)
  268. {
  269. Debug.DrawLine(ray3D.origin, ray3D.GetPoint(1000), Color.red);
  270. }
  271. if (Physics.Raycast(ray3D, out hit, float.PositiveInfinity, GetLayerMaskForMyLayer()))
  272. {
  273. //find if we hit this canvas - this needs to be uncommented
  274. if (overrideEventData && hit.collider.gameObject != this.gameObject && (colliderContainer == null || hit.collider.transform.parent != colliderContainer.transform))
  275. {
  276. o_canvasPos = Vector2.zero;
  277. return false;
  278. }
  279. //direction from the cyllinder center to the hit point
  280. Vector3 localHitPoint = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(hit.point);
  281. Vector3 directionFromCyllinderCenter = (localHitPoint - cyllinderMidPoint).normalized;
  282. //angle between middle of the projected canvas and hit point direction
  283. float angle = -AngleSigned(directionFromCyllinderCenter.ModifyY(0), mySettings.Angle < 0 ? Vector3.back : Vector3.forward, Vector3.up);
  284. //convert angle to canvas coordinates
  285. Vector2 canvasSize = myCanvas.GetComponent<RectTransform>().rect.size;
  286. //map the intersection point to 2d point in canvas space
  287. Vector2 pointOnCanvas = new Vector3(0, 0, 0);
  288. pointOnCanvas.x = angle.Remap(-mySettings.Angle / 2.0f, mySettings.Angle / 2.0f, -canvasSize.x / 2.0f, canvasSize.x / 2.0f);
  289. pointOnCanvas.y = localHitPoint.y;
  290. if (OutputInCanvasSpace)
  291. o_canvasPos = pointOnCanvas;
  292. else //convert the result to screen point in camera. This will be later used by raycaster and world camera to determine what we're pointing at
  293. o_canvasPos = myCanvas.worldCamera.WorldToScreenPoint(myCanvas.transform.localToWorldMatrix.MultiplyPoint3x4(pointOnCanvas));
  294. if (showDebug)
  295. {
  296. Debug.DrawLine(hit.point, hit.point.ModifyY(hit.point.y + 10), Color.green);
  297. Debug.DrawLine(hit.point, myCanvas.transform.localToWorldMatrix.MultiplyPoint3x4(cyllinderMidPoint), Color.yellow);
  298. }
  299. return true;
  300. }
  301. o_canvasPos = Vector2.zero;
  302. return false;
  303. }
  304. public virtual bool RaycastToCyllinderVerticalCanvas(Ray ray3D, out Vector2 o_canvasPos, bool OutputInCanvasSpace = false)
  305. {
  306. if (showDebug)
  307. {
  308. Debug.DrawLine(ray3D.origin, ray3D.GetPoint(1000), Color.red);
  309. }
  310. if (Physics.Raycast(ray3D, out hit, float.PositiveInfinity, GetLayerMaskForMyLayer()))
  311. {
  312. //find if we hit this canvas
  313. if (overrideEventData && hit.collider.gameObject != this.gameObject && (colliderContainer == null || hit.collider.transform.parent != colliderContainer.transform))
  314. {
  315. o_canvasPos = Vector2.zero;
  316. return false;
  317. }
  318. //direction from the cyllinder center to the hit point
  319. Vector3 localHitPoint = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(hit.point);
  320. Vector3 directionFromCyllinderCenter = (localHitPoint - cyllinderMidPoint).normalized;
  321. //angle between middle of the projected canvas and hit point direction
  322. float angle = -AngleSigned(directionFromCyllinderCenter.ModifyX(0), mySettings.Angle < 0 ? Vector3.back : Vector3.forward, Vector3.left);
  323. //convert angle to canvas coordinates
  324. Vector2 canvasSize = myCanvas.GetComponent<RectTransform>().rect.size;
  325. //map the intersection point to 2d point in canvas space
  326. Vector2 pointOnCanvas = new Vector3(0, 0, 0);
  327. pointOnCanvas.y = angle.Remap(-mySettings.Angle / 2.0f, mySettings.Angle / 2.0f, -canvasSize.y / 2.0f, canvasSize.y / 2.0f);
  328. pointOnCanvas.x = localHitPoint.x;
  329. if (OutputInCanvasSpace)
  330. o_canvasPos = pointOnCanvas;
  331. else //convert the result to screen point in camera. This will be later used by raycaster and world camera to determine what we're pointing at
  332. o_canvasPos = myCanvas.worldCamera.WorldToScreenPoint(myCanvas.transform.localToWorldMatrix.MultiplyPoint3x4(pointOnCanvas));
  333. if (showDebug)
  334. {
  335. Debug.DrawLine(hit.point, hit.point.ModifyY(hit.point.y + 10), Color.green);
  336. Debug.DrawLine(hit.point, myCanvas.transform.localToWorldMatrix.MultiplyPoint3x4(cyllinderMidPoint), Color.yellow);
  337. }
  338. return true;
  339. }
  340. o_canvasPos = Vector2.zero;
  341. return false;
  342. }
  343. public virtual bool RaycastToRingCanvas(Ray ray3D, out Vector2 o_canvasPos, bool OutputInCanvasSpace = false)
  344. {
  345. LayerMask myLayerMask = GetLayerMaskForMyLayer();
  346. if (Physics.Raycast(ray3D, out hit, float.PositiveInfinity, myLayerMask))
  347. {
  348. //find if we hit this canvas
  349. if (overrideEventData && hit.collider.gameObject != this.gameObject && (colliderContainer == null || hit.collider.transform.parent != colliderContainer.transform))
  350. {
  351. o_canvasPos = Vector2.zero;
  352. return false;
  353. }
  354. //local hit point on canvas and a direction from center
  355. Vector3 localHitPoint = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(hit.point);
  356. Vector3 directionFromRingCenter = localHitPoint.ModifyZ(0).normalized;
  357. Vector2 canvasSize = myCanvas.GetComponent<RectTransform>().rect.size;
  358. //angle between middle of the projected canvas and hit point direction from center
  359. float angle = -AngleSigned(directionFromRingCenter.ModifyZ(0), Vector3.up, Vector3.back);
  360. //map the intersection point to 2d point in canvas space
  361. Vector2 pointOnCanvas = new Vector2(0, 0);
  362. if (showDebug)
  363. Debug.Log("angle: " + angle);
  364. //map x coordinate based on angle between vector up and direction to hitpoint
  365. if (angle < 0)
  366. {
  367. pointOnCanvas.x = angle.Remap(0, -mySettings.Angle, -canvasSize.x / 2.0f, canvasSize.x / 2.0f);
  368. }
  369. else {
  370. pointOnCanvas.x = angle.Remap(360, 360 - mySettings.Angle, -canvasSize.x / 2.0f, canvasSize.x / 2.0f);
  371. }
  372. //map y coordinate based on hitpoint distance from the center and external diameter
  373. pointOnCanvas.y = localHitPoint.magnitude.Remap(mySettings.RingExternalDiameter * 0.5f * (1 - mySettings.RingFill), mySettings.RingExternalDiameter * 0.5f,
  374. -canvasSize.y * 0.5f * (mySettings.RingFlipVertical ? -1 : 1), canvasSize.y * 0.5f * (mySettings.RingFlipVertical ? -1 : 1));
  375. if (OutputInCanvasSpace)
  376. o_canvasPos = pointOnCanvas;
  377. else //convert the result to screen point in camera. This will be later used by raycaster and world camera to determine what we're pointing at
  378. o_canvasPos = myCanvas.worldCamera.WorldToScreenPoint(myCanvas.transform.localToWorldMatrix.MultiplyPoint3x4(pointOnCanvas));
  379. return true;
  380. }
  381. o_canvasPos = Vector2.zero;
  382. return false;
  383. }
  384. public virtual bool RaycastToSphereCanvas(Ray ray3D, out Vector2 o_canvasPos, bool OutputInCanvasSpace = false)
  385. {
  386. if (Physics.Raycast(ray3D, out hit, float.PositiveInfinity, GetLayerMaskForMyLayer()))
  387. {
  388. //find if we hit this canvas
  389. if (overrideEventData && hit.collider.gameObject != this.gameObject && (colliderContainer == null || hit.collider.transform.parent != colliderContainer.transform))
  390. {
  391. o_canvasPos = Vector2.zero;
  392. return false;
  393. }
  394. Vector2 canvasSize = myCanvas.GetComponent<RectTransform>().rect.size;
  395. float radius = (mySettings.PreserveAspect ? mySettings.GetCyllinderRadiusInCanvasSpace() : canvasSize.x / 2.0f);
  396. //local hit point on canvas, direction from its center and a vector perpendicular to direction, so we can use it to calculate its angle in both planes.
  397. Vector3 localHitPoint = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(hit.point);
  398. Vector3 SphereCenter = new Vector3(0, 0, mySettings.PreserveAspect ? -radius : 0);
  399. Vector3 directionFromSphereCenter = (localHitPoint - SphereCenter).normalized;
  400. Vector3 XZPlanePerpendicular = Vector3.Cross(directionFromSphereCenter, directionFromSphereCenter.ModifyY(0)).normalized * (directionFromSphereCenter.y < 0 ? 1 : -1);
  401. //horizontal and vertical angle between middle of the sphere and the hit point.
  402. //We do some fancy checks to determine vectors we compare them to,
  403. //to make sure they are negative on the left and bottom side of the canvas
  404. float hAngle = -AngleSigned(directionFromSphereCenter.ModifyY(0), (mySettings.Angle > 0 ? Vector3.forward : Vector3.back), (mySettings.Angle > 0 ? Vector3.up : Vector3.down));
  405. float vAngle = -AngleSigned(directionFromSphereCenter, directionFromSphereCenter.ModifyY(0), XZPlanePerpendicular);
  406. //find the size of the canvas expressed as measure of the arc it occupies on the sphere
  407. float hAngularSize = Mathf.Abs(mySettings.Angle) * 0.5f;
  408. float vAngularSize = Mathf.Abs(mySettings.PreserveAspect ? hAngularSize * canvasSize.y / canvasSize.x : mySettings.VerticalAngle * 0.5f);
  409. //map the intersection point to 2d point in canvas space
  410. Vector2 pointOnCanvas = new Vector2(hAngle.Remap(-hAngularSize, hAngularSize, -canvasSize.x * 0.5f, canvasSize.x * 0.5f),
  411. vAngle.Remap(-vAngularSize, vAngularSize, -canvasSize.y * 0.5f, canvasSize.y * 0.5f));
  412. if (showDebug)
  413. {
  414. Debug.Log("h: " + hAngle + " / v: " + vAngle + " poc: " + pointOnCanvas);
  415. Debug.DrawRay(myCanvas.transform.localToWorldMatrix.MultiplyPoint3x4(SphereCenter), myCanvas.transform.localToWorldMatrix.MultiplyVector(directionFromSphereCenter) * Mathf.Abs(radius), Color.red);
  416. Debug.DrawRay(myCanvas.transform.localToWorldMatrix.MultiplyPoint3x4(SphereCenter), myCanvas.transform.localToWorldMatrix.MultiplyVector(XZPlanePerpendicular) * 300, Color.magenta);
  417. }
  418. if (OutputInCanvasSpace)
  419. o_canvasPos = pointOnCanvas;
  420. else // convert the result to screen point in camera.This will be later used by raycaster and world camera to determine what we're pointing at
  421. o_canvasPos = myCanvas.worldCamera.WorldToScreenPoint(myCanvas.transform.localToWorldMatrix.MultiplyPoint3x4(pointOnCanvas));
  422. return true;
  423. }
  424. o_canvasPos = Vector2.zero;
  425. return false;
  426. }
  427. #endregion
  428. #region GRAPHIC RAYCASTING
  429. [NonSerialized]
  430. private List<Graphic> m_RaycastResults = new List<Graphic>();
  431. RaycastResult temp;
  432. void FlatRaycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
  433. {
  434. base.Raycast(eventData, resultAppendList);
  435. for (int i = 0; i < resultAppendList.Count; i++) {
  436. if (resultAppendList[i].module == this) {
  437. temp = resultAppendList[i];
  438. temp.worldPosition = hit.point;
  439. temp.worldNormal = hit.normal;
  440. resultAppendList[i] = temp;
  441. }
  442. }
  443. return;
  444. if (myCanvas == null) return; //no canvas?
  445. var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(myCanvas);
  446. if (canvasGraphics == null || canvasGraphics.Count == 0) return; // no graphics on canvas?
  447. //Multiple display handling-----------------------//
  448. int displayIndex;
  449. var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference instead
  450. if (myCanvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
  451. displayIndex = myCanvas.targetDisplay;
  452. else
  453. displayIndex = currentEventCamera.targetDisplay;
  454. var eventPosition = Display.RelativeMouseAt(eventData.position);
  455. if (eventPosition != Vector3.zero)
  456. {
  457. // Support for multiple display and display identification based on event position.
  458. int eventDisplayIndex = (int)eventPosition.z;
  459. // Discard events that are not part of this display so the user does not interact with multiple displays at once.
  460. if (eventDisplayIndex != displayIndex)
  461. return;
  462. }
  463. else
  464. {
  465. // The multiple display system is not supported on all platforms - returned index is 0 so default to the event data.
  466. //We will process the event assuming it occured in our display.
  467. eventPosition = eventData.position;
  468. }
  469. //Graphic Raycast ------------------------------------//
  470. //Perform a Graphic Raycast of all objects on the canvas and sort them by their depth.
  471. m_RaycastResults.Clear();
  472. FlatRaycastAndSort(myCanvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
  473. //create a ray going from camera, through pointer position
  474. Ray ray = new Ray();
  475. if (currentEventCamera != null)
  476. ray = currentEventCamera.ScreenPointToRay(eventPosition);
  477. float hitDistance = float.MaxValue;
  478. int totalCount = m_RaycastResults.Count;
  479. for (var index = 0; index < totalCount; index++)
  480. {
  481. var go = m_RaycastResults[index].gameObject;
  482. //Check to see if the go is behind the camera.
  483. //http://geomalgorithms.com/a06-_intersect-2.html
  484. Transform trans = go.transform;
  485. Vector3 transForward = trans.forward;
  486. float distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));
  487. if (distance < 0 || distance >= hitDistance) continue;
  488. //Add to cast result list
  489. var castResult = new RaycastResult
  490. {
  491. gameObject = go,
  492. module = this,
  493. distance = distance,
  494. screenPosition = eventPosition,
  495. index = resultAppendList.Count,
  496. depth = m_RaycastResults[index].depth,
  497. sortingLayer = myCanvas.sortingLayerID,
  498. sortingOrder = myCanvas.sortingOrder,
  499. worldPosition = hit.point,
  500. worldNormal = hit.normal
  501. };
  502. resultAppendList.Add(castResult);
  503. }
  504. }
  505. /// <summary>
  506. /// Perform a raycast into the screen and collect all graphics underneath it.
  507. /// </summary>
  508. [NonSerialized]
  509. static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
  510. private static void FlatRaycastAndSort(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
  511. {
  512. int totalCount = foundGraphics.Count;
  513. for (int i = 0; i < totalCount; ++i)
  514. {
  515. Graphic graphic = foundGraphics[i];
  516. // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
  517. if (graphic.depth == -1 || !graphic.raycastTarget || graphic.canvasRenderer.cull)
  518. continue;
  519. if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera))
  520. continue;
  521. if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
  522. continue;
  523. if (graphic.Raycast(pointerPosition, eventCamera)) s_SortedGraphics.Add(graphic);
  524. }
  525. s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
  526. totalCount = s_SortedGraphics.Count;
  527. for (int i = 0; i < totalCount; ++i)
  528. results.Add(s_SortedGraphics[i]);
  529. s_SortedGraphics.Clear();
  530. }
  531. #endregion
  532. #region COLLIDER MANAGEMENT
  533. /// <summary>
  534. /// Creates a mesh collider for curved canvas based on current angle and curve segments.
  535. /// </summary>
  536. /// <returns>The collider.</returns>
  537. protected void CreateCollider()
  538. {
  539. //remove all colliders on this object
  540. List<Collider> Cols = new List<Collider>();
  541. Cols.AddRange(this.GetComponents<Collider>());
  542. for (int i = 0; i < Cols.Count; i++)
  543. {
  544. Destroy(Cols[i]);
  545. }
  546. if (!mySettings.BlocksRaycasts) return; //null;
  547. if (mySettings.Shape == CurvedUISettings.CurvedUIShape.SPHERE && !mySettings.PreserveAspect && mySettings.VerticalAngle == 0) return;// null;
  548. //create a collider based on mapping type
  549. switch (mySettings.Shape)
  550. {
  551. case CurvedUISettings.CurvedUIShape.CYLINDER:
  552. {
  553. //creating a convex (lower performance - many parts) collider for when we have a rigidbody attached
  554. if (mySettings.ForceUseBoxCollider || GetComponent<Rigidbody>() != null || GetComponentInParent<Rigidbody>() != null)
  555. {
  556. if (colliderContainer != null)
  557. GameObject.Destroy(colliderContainer);
  558. colliderContainer = CreateConvexCyllinderCollider();
  559. }
  560. else // create a faster single mesh collier when possible
  561. {
  562. SetupMeshColliderUsingMesh(CreateCyllinderColliderMesh());
  563. }
  564. return;
  565. }
  566. case CurvedUISettings.CurvedUIShape.CYLINDER_VERTICAL:
  567. {
  568. //creating a convex (lower performance - many parts) collider for when we have a rigidbody attached
  569. if (mySettings.ForceUseBoxCollider || GetComponent<Rigidbody>() != null || GetComponentInParent<Rigidbody>() != null)
  570. {
  571. if (colliderContainer != null)
  572. GameObject.Destroy(colliderContainer);
  573. colliderContainer = CreateConvexCyllinderCollider(true);
  574. }
  575. else // create a faster single mesh collier when possible
  576. {
  577. SetupMeshColliderUsingMesh(CreateCyllinderColliderMesh(true));
  578. }
  579. return;
  580. }
  581. case CurvedUISettings.CurvedUIShape.RING:
  582. {
  583. BoxCollider col = this.gameObject.AddComponent<BoxCollider>();
  584. col.size = new Vector3(mySettings.RingExternalDiameter, mySettings.RingExternalDiameter, 1.0f);
  585. return;
  586. }
  587. case CurvedUISettings.CurvedUIShape.SPHERE:
  588. {
  589. //rigidbody in parent?
  590. if (GetComponent<Rigidbody>() != null || GetComponentInParent<Rigidbody>() != null)
  591. Debug.LogWarning("CurvedUI: Sphere shape canvases as children of rigidbodies do not support user input. Switch to Cyllinder shape or remove the rigidbody from parent.", this.gameObject);
  592. SetupMeshColliderUsingMesh(CreateSphereColliderMesh());
  593. return;
  594. }
  595. default:
  596. {
  597. return;
  598. }
  599. }
  600. }
  601. /// <summary>
  602. /// Adds neccessary components and fills them with given mesh data.
  603. /// </summary>
  604. /// <param name="meshie"></param>
  605. void SetupMeshColliderUsingMesh(Mesh meshie)
  606. {
  607. MeshFilter mf = this.AddComponentIfMissing<MeshFilter>();
  608. MeshCollider mc = this.gameObject.AddComponent<MeshCollider>();
  609. mf.mesh = meshie;
  610. mc.sharedMesh = meshie;
  611. }
  612. GameObject CreateConvexCyllinderCollider(bool vertical = false)
  613. {
  614. GameObject go = new GameObject("_CurvedUIColliders");
  615. go.layer = this.gameObject.layer;
  616. go.transform.SetParent(this.transform);
  617. go.transform.ResetTransform();
  618. Mesh meshie = new Mesh();
  619. Vector3[] Vertices = new Vector3[4];
  620. (myCanvas.transform as RectTransform).GetWorldCorners(Vertices);
  621. meshie.vertices = Vertices;
  622. //rearrange them to be in an easy to interpolate order and convert to canvas local spce
  623. if (vertical)
  624. {
  625. Vertices[0] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[1]);
  626. Vertices[1] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[2]);
  627. Vertices[2] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[0]);
  628. Vertices[3] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[3]);
  629. }
  630. else
  631. {
  632. Vertices[0] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[1]);
  633. Vertices[1] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[0]);
  634. Vertices[2] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[2]);
  635. Vertices[3] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[3]);
  636. }
  637. meshie.vertices = Vertices;
  638. //create a new array of vertices, subdivided as needed
  639. List<Vector3> verts = new List<Vector3>();
  640. int vertsCount = Mathf.Max(8, Mathf.RoundToInt(mySettings.BaseCircleSegments * Mathf.Abs(mySettings.Angle) / 360.0f));
  641. for (int i = 0; i < vertsCount; i++)
  642. {
  643. verts.Add(Vector3.Lerp(meshie.vertices[0], meshie.vertices[2], (i * 1.0f) / (vertsCount - 1)));
  644. }
  645. //curve the verts in canvas local space
  646. if (mySettings.Angle != 0)
  647. {
  648. Rect canvasRect = myCanvas.GetComponent<RectTransform>().rect;
  649. float radius = mySettings.GetCyllinderRadiusInCanvasSpace();
  650. for (int i = 0; i < verts.Count; i++)
  651. {
  652. Vector3 newpos = verts[i];
  653. if (vertical)
  654. {
  655. float theta = (verts[i].y / canvasRect.size.y) * mySettings.Angle * Mathf.Deg2Rad;
  656. newpos.y = Mathf.Sin(theta) * radius;
  657. newpos.z += Mathf.Cos(theta) * radius - radius;
  658. verts[i] = newpos;
  659. }
  660. else
  661. {
  662. float theta = (verts[i].x / canvasRect.size.x) * mySettings.Angle * Mathf.Deg2Rad;
  663. newpos.x = Mathf.Sin(theta) * radius;
  664. newpos.z += Mathf.Cos(theta) * radius - radius;
  665. verts[i] = newpos;
  666. }
  667. }
  668. }
  669. //create our box colliders and arrange them in a nice cyllinder
  670. float boxDepth = mySettings.GetTesslationSize(false).x / 10;
  671. for (int i = 0; i < verts.Count - 1; i++)
  672. {
  673. GameObject newBox = new GameObject("Box collider");
  674. newBox.layer = this.gameObject.layer;
  675. newBox.transform.SetParent(go.transform);
  676. newBox.transform.ResetTransform();
  677. newBox.AddComponent<BoxCollider>();
  678. if (vertical)
  679. {
  680. newBox.transform.localPosition = new Vector3(0, (verts[i + 1].y + verts[i].y) * 0.5f, (verts[i + 1].z + verts[i].z) * 0.5f);
  681. newBox.transform.localScale = new Vector3(boxDepth, Vector3.Distance(Vertices[0], Vertices[1]), Vector3.Distance(verts[i + 1], verts[i]));
  682. newBox.transform.localRotation = Quaternion.LookRotation((verts[i + 1] - verts[i]), Vertices[0] - Vertices[1]);
  683. }
  684. else
  685. {
  686. newBox.transform.localPosition = new Vector3((verts[i + 1].x + verts[i].x) * 0.5f, 0, (verts[i + 1].z + verts[i].z) * 0.5f);
  687. newBox.transform.localScale = new Vector3(boxDepth, Vector3.Distance(Vertices[0], Vertices[1]), Vector3.Distance(verts[i + 1], verts[i]));
  688. newBox.transform.localRotation = Quaternion.LookRotation((verts[i + 1] - verts[i]), Vertices[0] - Vertices[1]);
  689. }
  690. }
  691. return go;
  692. }
  693. Mesh CreateCyllinderColliderMesh(bool vertical = false)
  694. {
  695. Mesh meshie = new Mesh();
  696. Vector3[] Vertices = new Vector3[4];
  697. (myCanvas.transform as RectTransform).GetWorldCorners(Vertices);
  698. meshie.vertices = Vertices;
  699. //rearrange them to be in an easy to interpolate order and convert to canvas local spce
  700. if (vertical)
  701. {
  702. Vertices[0] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[1]);
  703. Vertices[1] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[2]);
  704. Vertices[2] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[0]);
  705. Vertices[3] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[3]);
  706. }
  707. else
  708. {
  709. Vertices[0] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[1]);
  710. Vertices[1] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[0]);
  711. Vertices[2] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[2]);
  712. Vertices[3] = myCanvas.transform.worldToLocalMatrix.MultiplyPoint3x4(meshie.vertices[3]);
  713. }
  714. meshie.vertices = Vertices;
  715. //create a new array of vertices, subdivided as needed
  716. List<Vector3> verts = new List<Vector3>();
  717. int vertsCount = Mathf.Max(8, Mathf.RoundToInt(mySettings.BaseCircleSegments * Mathf.Abs(mySettings.Angle) / 360.0f));
  718. for (int i = 0; i < vertsCount; i++)
  719. {
  720. verts.Add(Vector3.Lerp(meshie.vertices[0], meshie.vertices[2], (i * 1.0f) / (vertsCount - 1)));
  721. verts.Add(Vector3.Lerp(meshie.vertices[1], meshie.vertices[3], (i * 1.0f) / (vertsCount - 1)));
  722. }
  723. //curve the verts in canvas local space
  724. if (mySettings.Angle != 0)
  725. {
  726. Rect canvasRect = myCanvas.GetComponent<RectTransform>().rect;
  727. float radius = mySettings.GetCyllinderRadiusInCanvasSpace();
  728. for (int i = 0; i < verts.Count; i++)
  729. {
  730. Vector3 newpos = verts[i];
  731. if (vertical)
  732. {
  733. float theta = (verts[i].y / canvasRect.size.y) * mySettings.Angle * Mathf.Deg2Rad;
  734. newpos.y = Mathf.Sin(theta) * radius;
  735. newpos.z += Mathf.Cos(theta) * radius - radius;
  736. verts[i] = newpos;
  737. }
  738. else
  739. {
  740. float theta = (verts[i].x / canvasRect.size.x) * mySettings.Angle * Mathf.Deg2Rad;
  741. newpos.x = Mathf.Sin(theta) * radius;
  742. newpos.z += Mathf.Cos(theta) * radius - radius;
  743. verts[i] = newpos;
  744. }
  745. }
  746. }
  747. meshie.vertices = verts.ToArray();
  748. //create triangles drom verts
  749. List<int> tris = new List<int>();
  750. for (int i = 0; i < verts.Count / 2 - 1; i++)
  751. {
  752. if (vertical)
  753. {
  754. //forward tris
  755. tris.Add(i * 2 + 0);
  756. tris.Add(i * 2 + 1);
  757. tris.Add(i * 2 + 2);
  758. tris.Add(i * 2 + 1);
  759. tris.Add(i * 2 + 3);
  760. tris.Add(i * 2 + 2);
  761. }
  762. else {
  763. //forward tris
  764. tris.Add(i * 2 + 2);
  765. tris.Add(i * 2 + 1);
  766. tris.Add(i * 2 + 0);
  767. tris.Add(i * 2 + 2);
  768. tris.Add(i * 2 + 3);
  769. tris.Add(i * 2 + 1);
  770. }
  771. }
  772. meshie.triangles = tris.ToArray();
  773. return meshie;
  774. }
  775. Mesh CreateSphereColliderMesh()
  776. {
  777. Mesh meshie = new Mesh();
  778. Vector3[] Corners = new Vector3[4];
  779. (myCanvas.transform as RectTransform).GetWorldCorners(Corners);
  780. List<Vector3> verts = new List<Vector3>(Corners);
  781. for (int i = 0; i < verts.Count; i++)
  782. {
  783. verts[i] = mySettings.transform.worldToLocalMatrix.MultiplyPoint3x4(verts[i]);
  784. }
  785. if (mySettings.Angle != 0)
  786. {
  787. // Tesselate quads and apply transformation
  788. int startingVertexCount = verts.Count;
  789. for (int i = 0; i < startingVertexCount; i += 4)
  790. ModifyQuad(verts, i, mySettings.GetTesslationSize(false));
  791. // Remove old quads
  792. verts.RemoveRange(0, startingVertexCount);
  793. //curve verts
  794. float vangle = mySettings.VerticalAngle;
  795. float cylinder_angle = mySettings.Angle;
  796. Vector2 canvasSize = (myCanvas.transform as RectTransform).rect.size;
  797. float radius = mySettings.GetCyllinderRadiusInCanvasSpace();
  798. //caluclate vertical angle for aspect - consistent mapping
  799. if (mySettings.PreserveAspect)
  800. {
  801. vangle = mySettings.Angle * (canvasSize.y / canvasSize.x);
  802. }
  803. else {//if we're not going for constant aspect, set the width of the sphere to equal width of the original canvas
  804. radius = canvasSize.x / 2.0f;
  805. }
  806. //curve the vertices
  807. for (int i = 0; i < verts.Count; i++)
  808. {
  809. float theta = (verts[i].x / canvasSize.x).Remap(-0.5f, 0.5f, (180 - cylinder_angle) / 2.0f - 90, 180 - (180 - cylinder_angle) / 2.0f - 90);
  810. theta *= Mathf.Deg2Rad;
  811. float gamma = (verts[i].y / canvasSize.y).Remap(-0.5f, 0.5f, (180 - vangle) / 2.0f, 180 - (180 - vangle) / 2.0f);
  812. gamma *= Mathf.Deg2Rad;
  813. verts[i] = new Vector3(Mathf.Sin(gamma) * Mathf.Sin(theta) * radius,
  814. -radius * Mathf.Cos(gamma),
  815. Mathf.Sin(gamma) * Mathf.Cos(theta) * radius + (mySettings.PreserveAspect ? -radius : 0));
  816. }
  817. }
  818. meshie.vertices = verts.ToArray();
  819. //create triangles from verts
  820. List<int> tris = new List<int>();
  821. for (int i = 0; i < verts.Count; i += 4)
  822. {
  823. tris.Add(i + 0);
  824. tris.Add(i + 1);
  825. tris.Add(i + 2);
  826. tris.Add(i + 3);
  827. tris.Add(i + 0);
  828. tris.Add(i + 2);
  829. }
  830. meshie.triangles = tris.ToArray();
  831. return meshie;
  832. }
  833. #endregion
  834. #region SUPPORT FUNCTIONS
  835. bool IsInLayerMask(int layer, LayerMask layermask)
  836. {
  837. return layermask == (layermask | (1 << layer));
  838. }
  839. LayerMask GetLayerMaskForMyLayer() {
  840. int myLayerMask = -1;
  841. if (mySettings.RaycastMyLayerOnly)
  842. myLayerMask = 1 << this.gameObject.layer;
  843. return myLayerMask;
  844. }
  845. Image GazeProgressImage {
  846. get { return CurvedUIInputModule.Instance.GazeTimedClickProgressImage; }
  847. }
  848. /// <summary>
  849. /// Determine the signed angle between two vectors, with normal 'n'
  850. /// as the rotation axis.
  851. /// </summary>
  852. float AngleSigned(Vector3 v1, Vector3 v2, Vector3 n)
  853. {
  854. return Mathf.Atan2(
  855. Vector3.Dot(n, Vector3.Cross(v1, v2)),
  856. Vector3.Dot(v1, v2)) * Mathf.Rad2Deg;
  857. }
  858. private bool ShouldStartDrag(Vector2 pressPos, Vector2 currentPos, float threshold, bool useDragThreshold)
  859. {
  860. if (!useDragThreshold)
  861. return true;
  862. return (pressPos - currentPos).sqrMagnitude >= threshold * threshold;
  863. }
  864. protected virtual void ProcessMove(PointerEventData pointerEvent)
  865. {
  866. var targetGO = pointerEvent.pointerCurrentRaycast.gameObject;
  867. HandlePointerExitAndEnter(pointerEvent, targetGO);
  868. }
  869. protected void UpdateSelectedObjects(PointerEventData eventData)
  870. {
  871. //deselect last object if we moved beyond it
  872. bool selectedStillUnderGaze = false;
  873. foreach (GameObject go in eventData.hovered)
  874. {
  875. if (go == eventData.selectedObject)
  876. {
  877. selectedStillUnderGaze = true;
  878. break;
  879. }
  880. }
  881. if (!selectedStillUnderGaze) eventData.selectedObject = null;
  882. //find new object to select in hovered objects
  883. foreach (GameObject go in eventData.hovered)
  884. {
  885. if (go == null) continue;
  886. //go through only go that can be selected and are drawn by the canvas
  887. gph = go.GetComponent<Graphic>();
  888. #if UNITY_5_1
  889. if (go.GetComponent<Selectable>() != null && gph != null && gph.depth != -1)
  890. #else
  891. if (go.GetComponent<Selectable>() != null && gph != null && gph.depth != -1 && gph.raycastTarget)
  892. #endif
  893. {
  894. if (eventData.selectedObject != go)
  895. eventData.selectedObject = go;
  896. break;
  897. }
  898. }
  899. if (mySettings.ControlMethod == CurvedUIInputModule.CUIControlMethod.GAZE)
  900. {
  901. //Test for selected object being dragged and initialize dragging, if needed.
  902. //We do this here to trick unity's StandAloneInputModule into thinking we used a touch or mouse to do it.
  903. if (eventData.IsPointerMoving() && eventData.pointerDrag != null
  904. && !eventData.dragging
  905. && ShouldStartDrag(eventData.pressPosition, eventData.position, EventSystem.current.pixelDragThreshold, eventData.useDragThreshold))
  906. {
  907. ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.beginDragHandler);
  908. eventData.dragging = true;
  909. }
  910. }
  911. }
  912. // walk up the tree till a common root between the last entered and the current entered is foung
  913. // send exit events up to (but not inluding) the common root. Then send enter events up to
  914. // (but not including the common root).
  915. protected void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget)
  916. {
  917. // if we have no target / pointerEnter has been deleted
  918. // just send exit events to anything we are tracking
  919. // then exit
  920. if (newEnterTarget == null || currentPointerData.pointerEnter == null)
  921. {
  922. for (var i = 0; i < currentPointerData.hovered.Count; ++i)
  923. ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerExitHandler);
  924. currentPointerData.hovered.Clear();
  925. if (newEnterTarget == null)
  926. {
  927. currentPointerData.pointerEnter = newEnterTarget;
  928. return;
  929. }
  930. }
  931. // if we have not changed hover target
  932. if (currentPointerData.pointerEnter == newEnterTarget && newEnterTarget)
  933. return;
  934. GameObject commonRoot = FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget);
  935. // and we already an entered object from last time
  936. if (currentPointerData.pointerEnter != null)
  937. {
  938. // send exit handler call to all elements in the chain
  939. // until we reach the new target, or null!
  940. Transform t = currentPointerData.pointerEnter.transform;
  941. while (t != null)
  942. {
  943. // if we reach the common root break out!
  944. if (commonRoot != null && commonRoot.transform == t)
  945. break;
  946. ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler);
  947. currentPointerData.hovered.Remove(t.gameObject);
  948. t = t.parent;
  949. }
  950. }
  951. // now issue the enter call up to but not including the common root
  952. currentPointerData.pointerEnter = newEnterTarget;
  953. if (newEnterTarget != null)
  954. {
  955. Transform t = newEnterTarget.transform;
  956. while (t != null && t.gameObject != commonRoot)
  957. {
  958. ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerEnterHandler);
  959. currentPointerData.hovered.Add(t.gameObject);
  960. t = t.parent;
  961. }
  962. }
  963. }
  964. protected static GameObject FindCommonRoot(GameObject g1, GameObject g2)
  965. {
  966. if (g1 == null || g2 == null)
  967. return null;
  968. var t1 = g1.transform;
  969. while (t1 != null)
  970. {
  971. var t2 = g2.transform;
  972. while (t2 != null)
  973. {
  974. if (t1 == t2)
  975. return t1.gameObject;
  976. t2 = t2.parent;
  977. }
  978. t1 = t1.parent;
  979. }
  980. return null;
  981. }
  982. /// <summary>
  983. /// REturns a screen point under which a ray intersects the curved canvas in its event camera view
  984. /// </summary>
  985. /// <returns><c>true</c>, if screen space point by ray was gotten, <c>false</c> otherwise.</returns>
  986. /// <param name="ray">Ray.</param>
  987. /// <param name="o_positionOnCanvas">O position on canvas.</param>
  988. bool GetScreenSpacePointByRay(Ray ray, out Vector2 o_positionOnCanvas)
  989. {
  990. switch (mySettings.Shape)
  991. {
  992. case CurvedUISettings.CurvedUIShape.CYLINDER:
  993. {
  994. return RaycastToCyllinderCanvas(ray, out o_positionOnCanvas, false);
  995. }
  996. case CurvedUISettings.CurvedUIShape.CYLINDER_VERTICAL:
  997. {
  998. return RaycastToCyllinderVerticalCanvas(ray, out o_positionOnCanvas, false);
  999. }
  1000. case CurvedUISettings.CurvedUIShape.RING:
  1001. {
  1002. return RaycastToRingCanvas(ray, out o_positionOnCanvas, false);
  1003. }
  1004. case CurvedUISettings.CurvedUIShape.SPHERE:
  1005. {
  1006. return RaycastToSphereCanvas(ray, out o_positionOnCanvas, false);
  1007. }
  1008. default:
  1009. {
  1010. o_positionOnCanvas = Vector2.zero;
  1011. return false;
  1012. }
  1013. }
  1014. }
  1015. bool CheckEventCamera()
  1016. {
  1017. //check if we have a world camera to process events by
  1018. if (myCanvas.worldCamera == null)
  1019. {
  1020. //try assigning from InputModule
  1021. if (CurvedUIInputModule.Instance && CurvedUIInputModule.Instance.EventCamera)
  1022. myCanvas.worldCamera = CurvedUIInputModule.Instance.EventCamera;
  1023. else if (Camera.main) //asign Main Camera
  1024. myCanvas.worldCamera = Camera.main;
  1025. }
  1026. return myCanvas.worldCamera != null;
  1027. }
  1028. #endregion
  1029. #region PUBLIC
  1030. /// <summary>
  1031. /// Returns true if user's pointer is currently pointing inside this canvas.
  1032. /// </summary>
  1033. public bool PointingAtCanvas {
  1034. get { return pointingAtCanvas; }
  1035. }
  1036. public void RebuildCollider()
  1037. {
  1038. cyllinderMidPoint = new Vector3(0, 0, -mySettings.GetCyllinderRadiusInCanvasSpace());
  1039. CreateCollider();
  1040. }
  1041. /// <summary>
  1042. /// Returns all objects currently under the pointer
  1043. /// </summary>
  1044. /// <returns>The objects under pointer.</returns>
  1045. public List<GameObject> GetObjectsUnderPointer()
  1046. {
  1047. if (objectsUnderPointer == null) objectsUnderPointer = new List<GameObject>();
  1048. return objectsUnderPointer;
  1049. }
  1050. /// <summary>
  1051. /// Returns all the canvas objects that are visible under given Screen Position of EventCamera
  1052. /// </summary>
  1053. public List<GameObject> GetObjectsUnderScreenPos(Vector2 screenPos, Camera eventCamera = null)
  1054. {
  1055. if (eventCamera == null)
  1056. eventCamera = myCanvas.worldCamera;
  1057. return GetObjectsHitByRay(eventCamera.ScreenPointToRay(screenPos));
  1058. }
  1059. /// <summary>
  1060. /// Returns all the canvas objects that are intersected by given ray
  1061. /// </summary>
  1062. /// <returns>The objects hit by ray.</returns>
  1063. /// <param name="ray">Ray.</param>
  1064. public List<GameObject> GetObjectsHitByRay(Ray ray)
  1065. {
  1066. List<GameObject> results = new List<GameObject>();
  1067. Vector2 pointerPosition;
  1068. //ray outside the canvas, return null
  1069. if (!GetScreenSpacePointByRay(ray, out pointerPosition))
  1070. return results;
  1071. //lets find the graphics under ray!
  1072. List<Graphic> s_SortedGraphics = new List<Graphic>();
  1073. var foundGraphics = GraphicRegistry.GetGraphicsForCanvas(myCanvas);
  1074. for (int i = 0; i < foundGraphics.Count; ++i)
  1075. {
  1076. Graphic graphic = foundGraphics[i];
  1077. // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
  1078. if (graphic.depth == -1 || !graphic.raycastTarget)
  1079. continue;
  1080. if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera))
  1081. continue;
  1082. if (graphic.Raycast(pointerPosition, eventCamera))
  1083. s_SortedGraphics.Add(graphic);
  1084. }
  1085. s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
  1086. for (int i = 0; i < s_SortedGraphics.Count; ++i)
  1087. results.Add(s_SortedGraphics[i].gameObject);
  1088. s_SortedGraphics.Clear();
  1089. return results;
  1090. }
  1091. /// <summary>
  1092. /// Sends OnClick event to every Button under pointer.
  1093. /// </summary>
  1094. public void Click()
  1095. {
  1096. for (int i = 0; i < GetObjectsUnderPointer().Count; i++)
  1097. {
  1098. if (GetObjectsUnderPointer()[i].GetComponent<Slider>())//slider requires a diffrent way to click.
  1099. {
  1100. //Click calculated via RectTransformUtility - that's the way Slider class does it under the hood.
  1101. Slider m_slider = GetObjectsUnderPointer()[i].GetComponent<Slider>();
  1102. Vector2 clickPoint;
  1103. RectTransformUtility.ScreenPointToLocalPointInRectangle((m_slider.handleRect.parent as RectTransform), lastFrameEventData.position, myCanvas.worldCamera, out clickPoint);
  1104. clickPoint -= m_slider.handleRect.parent.GetComponent<RectTransform>().rect.position;
  1105. if (m_slider.direction == Slider.Direction.LeftToRight || m_slider.direction == Slider.Direction.RightToLeft)
  1106. m_slider.normalizedValue = clickPoint.x / (m_slider.handleRect.parent as RectTransform).rect.width;
  1107. else
  1108. m_slider.normalizedValue = clickPoint.y / (m_slider.handleRect.parent as RectTransform).rect.height;
  1109. //prompt update from fill Graphic to avoid flicker
  1110. GetObjectsUnderPointer()[i].GetComponent<Slider>().fillRect.GetComponent<Graphic>().SetAllDirty();
  1111. //log
  1112. //Debug.Log("x: " + clickPoint.x + ", width:" + (m_slider.transform as RectTransform).rect.width + ", value:" + clickPoint.x / (m_slider.transform as RectTransform).rect.width);
  1113. }
  1114. else
  1115. {
  1116. ExecuteEvents.Execute(GetObjectsUnderPointer()[i], lastFrameEventData, ExecuteEvents.pointerDownHandler);
  1117. ExecuteEvents.Execute(GetObjectsUnderPointer()[i], lastFrameEventData, ExecuteEvents.pointerClickHandler);
  1118. ExecuteEvents.Execute(GetObjectsUnderPointer()[i], lastFrameEventData, ExecuteEvents.pointerUpHandler);
  1119. }
  1120. }
  1121. }
  1122. #endregion
  1123. #region TESSELATION
  1124. void ModifyQuad(List<Vector3> verts, int vertexIndex, Vector2 requiredSize)
  1125. {
  1126. // Read the existing quad vertices
  1127. List<Vector3> quad = new List<Vector3>();
  1128. for (int i = 0; i < 4; i++)
  1129. quad.Add(verts[vertexIndex + i]);
  1130. // horizotal and vertical directions of a quad. We're going to tesselate parallel to these.
  1131. Vector3 horizontalDir = quad[2] - quad[1];
  1132. Vector3 verticalDir = quad[1] - quad[0];
  1133. // Find how many quads we need to create
  1134. int horizontalQuads = Mathf.CeilToInt(horizontalDir.magnitude * (1.0f / Mathf.Max(1.0f, requiredSize.x)));
  1135. int verticalQuads = Mathf.CeilToInt(verticalDir.magnitude * (1.0f / Mathf.Max(1.0f, requiredSize.y)));
  1136. // Create the quads!
  1137. float yStart = 0.0f;
  1138. for (int y = 0; y < verticalQuads; ++y)
  1139. {
  1140. float yEnd = (y + 1.0f) / verticalQuads;
  1141. float xStart = 0.0f;
  1142. for (int x = 0; x < horizontalQuads; ++x)
  1143. {
  1144. float xEnd = (x + 1.0f) / horizontalQuads;
  1145. //Add new quads to list
  1146. verts.Add(TesselateQuad(quad, xStart, yStart));
  1147. verts.Add(TesselateQuad(quad, xStart, yEnd));
  1148. verts.Add(TesselateQuad(quad, xEnd, yEnd));
  1149. verts.Add(TesselateQuad(quad, xEnd, yStart));
  1150. //begin the next quad where we ened this one
  1151. xStart = xEnd;
  1152. }
  1153. //begin the next row where we ended this one
  1154. yStart = yEnd;
  1155. }
  1156. }
  1157. Vector3 TesselateQuad(List<Vector3> quad, float x, float y)
  1158. {
  1159. Vector3 ret = Vector3.zero;
  1160. //1. calculate weighting factors
  1161. List<float> weights = new List<float>(){
  1162. (1-x) * (1-y),
  1163. (1-x) * y,
  1164. x * y,
  1165. x * (1-y),
  1166. };
  1167. //2. interpolate pos using weighting factors
  1168. for (int i = 0; i < 4; i++)
  1169. ret += quad[i] * weights[i];
  1170. return ret;
  1171. }
  1172. #endregion
  1173. }
  1174. }