Assignment for RMIT Mixed Reality in 2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

504 lines
19 KiB

  1. // Panel Menu|Prefabs|0130
  2. namespace VRTK
  3. {
  4. using System.Collections;
  5. using UnityEngine;
  6. /// <summary>
  7. /// Adds a top-level controller to handle the display of up to four child PanelMenuItemController items which are displayed as a canvas UI panel.
  8. /// </summary>
  9. /// <remarks>
  10. /// **Prefab Usage:**
  11. /// * Place the `VRTK/Prefabs/PanelMenu/PanelMenu` prefab as a child of the `VRTK_InteractableObject` the panel menu is for.
  12. /// * Optionally remove the panel control menu item child GameObjects if they are not required, e.g. `PanelTopControls`.
  13. /// * Set the panel menu item controllers on the `VRTK_PanelMenuController` script to determine which panel control menu items are available.
  14. /// * The available panel control menu items can be activated by pressing the corresponding direction on the touchpad.
  15. /// </remarks>
  16. /// <example>
  17. /// `040_Controls_Panel_Menu` contains three basic interactive object examples of the PanelMenu in use.
  18. /// </example>
  19. public class VRTK_PanelMenuController : MonoBehaviour
  20. {
  21. public enum TouchpadPressPosition
  22. {
  23. None,
  24. Top,
  25. Bottom,
  26. Left,
  27. Right
  28. }
  29. [Tooltip("The GameObject the panel should rotate towards, which is the Camera (eye) by default.")]
  30. public GameObject rotateTowards;
  31. [Tooltip("The scale multiplier, which relates to the scale of parent interactable object.")]
  32. public float zoomScaleMultiplier = 1f;
  33. [Tooltip("The top PanelMenuItemController, which is triggered by pressing up on the controller touchpad.")]
  34. public VRTK_PanelMenuItemController topPanelMenuItemController;
  35. [Tooltip("The bottom PanelMenuItemController, which is triggered by pressing down on the controller touchpad.")]
  36. public VRTK_PanelMenuItemController bottomPanelMenuItemController;
  37. [Tooltip("The left PanelMenuItemController, which is triggered by pressing left on the controller touchpad.")]
  38. public VRTK_PanelMenuItemController leftPanelMenuItemController;
  39. [Tooltip("The right PanelMenuItemController, which is triggered by pressing right on the controller touchpad.")]
  40. public VRTK_PanelMenuItemController rightPanelMenuItemController;
  41. // Relates to scale of canvas on panel items.
  42. protected const float CanvasScaleSize = 0.001f;
  43. // Swipe sensitivity / detection.
  44. protected const float AngleTolerance = 30f;
  45. protected const float SwipeMinDist = 0.2f;
  46. protected const float SwipeMinVelocity = 4.0f;
  47. protected VRTK_ControllerEvents controllerEvents;
  48. protected VRTK_PanelMenuItemController currentPanelMenuItemController;
  49. protected GameObject interactableObject;
  50. protected GameObject canvasObject;
  51. protected readonly Vector2 xAxis = new Vector2(1, 0);
  52. protected readonly Vector2 yAxis = new Vector2(0, 1);
  53. protected Vector2 touchStartPosition;
  54. protected Vector2 touchEndPosition;
  55. protected float touchStartTime;
  56. protected float currentAngle;
  57. protected bool isTrackingSwipe = false;
  58. protected bool isPendingSwipeCheck = false;
  59. protected bool isGrabbed = false;
  60. protected bool isShown = false;
  61. protected Coroutine tweenMenuScaleRoutine;
  62. /// <summary>
  63. /// The ToggleMenu method is used to show or hide the menu.
  64. /// </summary>
  65. public virtual void ToggleMenu()
  66. {
  67. if (isShown)
  68. {
  69. HideMenu(true);
  70. }
  71. else
  72. {
  73. ShowMenu();
  74. }
  75. }
  76. /// <summary>
  77. /// The ShowMenu method is used to show the menu.
  78. /// </summary>
  79. public virtual void ShowMenu()
  80. {
  81. if (!isShown)
  82. {
  83. isShown = true;
  84. InitTweenMenuScale(isShown);
  85. }
  86. }
  87. /// <summary>
  88. /// The HideMenu method is used to hide the menu.
  89. /// </summary>
  90. /// <param name="force">If true then the menu is always hidden.</param>
  91. public virtual void HideMenu(bool force)
  92. {
  93. if (isShown && force)
  94. {
  95. isShown = false;
  96. InitTweenMenuScale(isShown);
  97. }
  98. }
  99. /// <summary>
  100. /// The HideMenuImmediate method is used to immediately hide the menu.
  101. /// </summary>
  102. public virtual void HideMenuImmediate()
  103. {
  104. if (currentPanelMenuItemController != null && isShown)
  105. {
  106. HandlePanelMenuItemControllerVisibility(currentPanelMenuItemController);
  107. }
  108. transform.localScale = Vector3.zero;
  109. canvasObject.transform.localScale = Vector3.zero;
  110. isShown = false;
  111. }
  112. protected virtual void Awake()
  113. {
  114. Initialize();
  115. VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
  116. }
  117. protected virtual void Start()
  118. {
  119. interactableObject = gameObject.transform.parent.gameObject;
  120. if (interactableObject == null || interactableObject.GetComponent<VRTK_InteractableObject>() == null)
  121. {
  122. VRTK_Logger.Warn(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "PanelMenuController", "VRTK_InteractableObject", "a parent"));
  123. return;
  124. }
  125. interactableObject.GetComponent<VRTK_InteractableObject>().InteractableObjectGrabbed += new InteractableObjectEventHandler(DoInteractableObjectIsGrabbed);
  126. interactableObject.GetComponent<VRTK_InteractableObject>().InteractableObjectUngrabbed += new InteractableObjectEventHandler(DoInteractableObjectIsUngrabbed);
  127. canvasObject = gameObject.transform.GetChild(0).gameObject;
  128. if (canvasObject == null || canvasObject.GetComponent<Canvas>() == null)
  129. {
  130. VRTK_Logger.Warn(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "PanelMenuController", "Canvas", "a child"));
  131. }
  132. }
  133. protected virtual void OnDestroy()
  134. {
  135. VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
  136. }
  137. protected virtual void Update()
  138. {
  139. if (interactableObject != null)
  140. {
  141. if (rotateTowards == null)
  142. {
  143. rotateTowards = VRTK_DeviceFinder.HeadsetTransform().gameObject;
  144. if (rotateTowards == null)
  145. {
  146. VRTK_Logger.Warn(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.COULD_NOT_FIND_OBJECT_FOR_ACTION, "PanelMenuController", "an object", "rotate towards"));
  147. }
  148. }
  149. if (isShown)
  150. {
  151. if (rotateTowards != null)
  152. {
  153. transform.rotation = Quaternion.LookRotation((rotateTowards.transform.position - transform.position) * -1, Vector3.up);
  154. }
  155. }
  156. if (isPendingSwipeCheck)
  157. {
  158. CalculateSwipeAction();
  159. }
  160. }
  161. }
  162. protected virtual void Initialize()
  163. {
  164. if (Application.isPlaying)
  165. {
  166. if (!isShown)
  167. {
  168. transform.localScale = Vector3.zero;
  169. }
  170. }
  171. if (controllerEvents == null)
  172. {
  173. transform.localPosition = new Vector3(transform.localPosition.x, transform.localPosition.y, transform.localPosition.z);
  174. controllerEvents = GetComponentInParent<VRTK_ControllerEvents>();
  175. }
  176. }
  177. protected virtual void BindControllerEvents()
  178. {
  179. controllerEvents.TouchpadPressed += new ControllerInteractionEventHandler(DoTouchpadPress);
  180. controllerEvents.TouchpadTouchStart += new ControllerInteractionEventHandler(DoTouchpadTouched);
  181. controllerEvents.TouchpadTouchEnd += new ControllerInteractionEventHandler(DoTouchpadUntouched);
  182. controllerEvents.TouchpadAxisChanged += new ControllerInteractionEventHandler(DoTouchpadAxisChanged);
  183. controllerEvents.TriggerPressed += new ControllerInteractionEventHandler(DoTriggerPressed);
  184. }
  185. protected virtual void UnbindControllerEvents()
  186. {
  187. controllerEvents.TouchpadPressed -= new ControllerInteractionEventHandler(DoTouchpadPress);
  188. controllerEvents.TouchpadTouchStart -= new ControllerInteractionEventHandler(DoTouchpadTouched);
  189. controllerEvents.TouchpadTouchEnd -= new ControllerInteractionEventHandler(DoTouchpadUntouched);
  190. controllerEvents.TouchpadAxisChanged -= new ControllerInteractionEventHandler(DoTouchpadAxisChanged);
  191. controllerEvents.TriggerPressed -= new ControllerInteractionEventHandler(DoTriggerPressed);
  192. }
  193. protected virtual void HandlePanelMenuItemControllerVisibility(VRTK_PanelMenuItemController targetPanelItemController)
  194. {
  195. if (isShown)
  196. {
  197. if (currentPanelMenuItemController == targetPanelItemController)
  198. {
  199. targetPanelItemController.Hide(interactableObject);
  200. currentPanelMenuItemController = null;
  201. HideMenu(true);
  202. }
  203. else
  204. {
  205. currentPanelMenuItemController.Hide(interactableObject);
  206. currentPanelMenuItemController = targetPanelItemController;
  207. }
  208. }
  209. else
  210. {
  211. currentPanelMenuItemController = targetPanelItemController;
  212. }
  213. if (currentPanelMenuItemController != null)
  214. {
  215. currentPanelMenuItemController.Show(interactableObject);
  216. ShowMenu();
  217. }
  218. }
  219. protected virtual void InitTweenMenuScale(bool show)
  220. {
  221. if (tweenMenuScaleRoutine != null)
  222. {
  223. StopCoroutine(tweenMenuScaleRoutine);
  224. }
  225. if (enabled)
  226. {
  227. tweenMenuScaleRoutine = StartCoroutine(TweenMenuScale(show));
  228. }
  229. }
  230. protected virtual IEnumerator TweenMenuScale(bool show)
  231. {
  232. float targetScale = 0;
  233. Vector3 direction = -1 * Vector3.one;
  234. if (show)
  235. {
  236. canvasObject.transform.localScale = new Vector3(CanvasScaleSize, CanvasScaleSize, CanvasScaleSize);
  237. targetScale = zoomScaleMultiplier;
  238. direction = Vector3.one;
  239. }
  240. int i = 0;
  241. while (i < 250 && ((show && transform.localScale.x < targetScale) || (!show && transform.localScale.x > targetScale)))
  242. {
  243. transform.localScale += direction * Time.deltaTime * 4f * zoomScaleMultiplier;
  244. yield return true;
  245. i++;
  246. }
  247. transform.localScale = direction * targetScale;
  248. if (!show)
  249. {
  250. canvasObject.transform.localScale = Vector3.zero;
  251. }
  252. }
  253. protected virtual void DoInteractableObjectIsGrabbed(object sender, InteractableObjectEventArgs e)
  254. {
  255. controllerEvents = e.interactingObject.GetComponentInParent<VRTK_ControllerEvents>();
  256. if (controllerEvents != null)
  257. {
  258. BindControllerEvents();
  259. }
  260. isGrabbed = true;
  261. }
  262. protected virtual void DoInteractableObjectIsUngrabbed(object sender, InteractableObjectEventArgs e)
  263. {
  264. isGrabbed = false;
  265. if (isShown)
  266. {
  267. HideMenuImmediate();
  268. }
  269. if (controllerEvents != null)
  270. {
  271. UnbindControllerEvents();
  272. controllerEvents = null;
  273. }
  274. }
  275. protected virtual void DoTouchpadPress(object sender, ControllerInteractionEventArgs e)
  276. {
  277. if (isGrabbed)
  278. {
  279. TouchpadPressPosition pressPosition = CalculateTouchpadPressPosition();
  280. switch (pressPosition)
  281. {
  282. case TouchpadPressPosition.Top:
  283. if (topPanelMenuItemController != null)
  284. {
  285. HandlePanelMenuItemControllerVisibility(topPanelMenuItemController);
  286. }
  287. break;
  288. case TouchpadPressPosition.Bottom:
  289. if (bottomPanelMenuItemController != null)
  290. {
  291. HandlePanelMenuItemControllerVisibility(bottomPanelMenuItemController);
  292. }
  293. break;
  294. case TouchpadPressPosition.Left:
  295. if (leftPanelMenuItemController != null)
  296. {
  297. HandlePanelMenuItemControllerVisibility(leftPanelMenuItemController);
  298. }
  299. break;
  300. case TouchpadPressPosition.Right:
  301. if (rightPanelMenuItemController != null)
  302. {
  303. HandlePanelMenuItemControllerVisibility(rightPanelMenuItemController);
  304. }
  305. break;
  306. }
  307. }
  308. }
  309. protected virtual void DoTouchpadTouched(object sender, ControllerInteractionEventArgs e)
  310. {
  311. touchStartPosition = new Vector2(e.touchpadAxis.x, e.touchpadAxis.y);
  312. touchStartTime = Time.time;
  313. isTrackingSwipe = true;
  314. }
  315. protected virtual void DoTouchpadUntouched(object sender, ControllerInteractionEventArgs e)
  316. {
  317. isTrackingSwipe = false;
  318. isPendingSwipeCheck = true;
  319. }
  320. protected virtual void DoTouchpadAxisChanged(object sender, ControllerInteractionEventArgs e)
  321. {
  322. ChangeAngle(CalculateAngle(e));
  323. if (isTrackingSwipe)
  324. {
  325. touchEndPosition = new Vector2(e.touchpadAxis.x, e.touchpadAxis.y);
  326. }
  327. }
  328. protected virtual void DoTriggerPressed(object sender, ControllerInteractionEventArgs e)
  329. {
  330. if (isGrabbed)
  331. {
  332. OnTriggerPressed();
  333. }
  334. }
  335. protected virtual void ChangeAngle(float angle, object sender = null)
  336. {
  337. currentAngle = angle;
  338. }
  339. protected virtual void CalculateSwipeAction()
  340. {
  341. isPendingSwipeCheck = false;
  342. float deltaTime = Time.time - touchStartTime;
  343. Vector2 swipeVector = touchEndPosition - touchStartPosition;
  344. float velocity = swipeVector.magnitude / deltaTime;
  345. if ((velocity > SwipeMinVelocity) && (swipeVector.magnitude > SwipeMinDist))
  346. {
  347. swipeVector.Normalize();
  348. float angleOfSwipe = Vector2.Dot(swipeVector, xAxis);
  349. angleOfSwipe = Mathf.Acos(angleOfSwipe) * Mathf.Rad2Deg;
  350. // Left / right
  351. if (angleOfSwipe < AngleTolerance)
  352. {
  353. OnSwipeRight();
  354. }
  355. else if ((180.0f - angleOfSwipe) < AngleTolerance)
  356. {
  357. OnSwipeLeft();
  358. }
  359. else
  360. {
  361. // Top / bottom
  362. angleOfSwipe = Vector2.Dot(swipeVector, yAxis);
  363. angleOfSwipe = Mathf.Acos(angleOfSwipe) * Mathf.Rad2Deg;
  364. if (angleOfSwipe < AngleTolerance)
  365. {
  366. OnSwipeTop();
  367. }
  368. else if ((180.0f - angleOfSwipe) < AngleTolerance)
  369. {
  370. OnSwipeBottom();
  371. }
  372. }
  373. }
  374. }
  375. protected virtual TouchpadPressPosition CalculateTouchpadPressPosition()
  376. {
  377. if (CheckAnglePosition(currentAngle, AngleTolerance, 0))
  378. {
  379. return TouchpadPressPosition.Top;
  380. }
  381. else if (CheckAnglePosition(currentAngle, AngleTolerance, 180))
  382. {
  383. return TouchpadPressPosition.Bottom;
  384. }
  385. else if (CheckAnglePosition(currentAngle, AngleTolerance, 270))
  386. {
  387. return TouchpadPressPosition.Left;
  388. }
  389. else if (CheckAnglePosition(currentAngle, AngleTolerance, 90))
  390. {
  391. return TouchpadPressPosition.Right;
  392. }
  393. return TouchpadPressPosition.None;
  394. }
  395. protected virtual void OnSwipeLeft()
  396. {
  397. if (currentPanelMenuItemController != null)
  398. {
  399. currentPanelMenuItemController.SwipeLeft(interactableObject);
  400. }
  401. }
  402. protected virtual void OnSwipeRight()
  403. {
  404. if (currentPanelMenuItemController != null)
  405. {
  406. currentPanelMenuItemController.SwipeRight(interactableObject);
  407. }
  408. }
  409. protected virtual void OnSwipeTop()
  410. {
  411. if (currentPanelMenuItemController != null)
  412. {
  413. currentPanelMenuItemController.SwipeTop(interactableObject);
  414. }
  415. }
  416. protected virtual void OnSwipeBottom()
  417. {
  418. if (currentPanelMenuItemController != null)
  419. {
  420. currentPanelMenuItemController.SwipeBottom(interactableObject);
  421. }
  422. }
  423. protected virtual void OnTriggerPressed()
  424. {
  425. if (currentPanelMenuItemController != null)
  426. {
  427. currentPanelMenuItemController.TriggerPressed(interactableObject);
  428. }
  429. }
  430. protected virtual float CalculateAngle(ControllerInteractionEventArgs e)
  431. {
  432. return e.touchpadAngle;
  433. }
  434. protected virtual float NormAngle(float currentDegree, float maxAngle = 360)
  435. {
  436. if (currentDegree < 0) currentDegree = currentDegree + maxAngle;
  437. return currentDegree % maxAngle;
  438. }
  439. protected virtual bool CheckAnglePosition(float currentDegree, float tolerance, float targetDegree)
  440. {
  441. float lowerBound = NormAngle(currentDegree - tolerance);
  442. float upperBound = NormAngle(currentDegree + tolerance);
  443. if (lowerBound > upperBound)
  444. {
  445. return targetDegree >= lowerBound || targetDegree <= upperBound;
  446. }
  447. return targetDegree >= lowerBound && targetDegree <= upperBound;
  448. }
  449. }
  450. }