ChooseTrackedObjectMenu.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.Events;
  6. #if ZED_STEAM_VR
  7. using Valve.VR;
  8. #endif
  9. /// <summary>
  10. /// Lets the user choose the tracked object that the ZED is anchored to, for the MR calibration scene.
  11. /// Works by spawning a prefab button for each tracked object, complete with graphics, and arranging them
  12. /// into a grid. Each is assigned a callback that will enable the scene's CameraAnchor object and set its
  13. /// tracked object accordingly.
  14. /// </summary>
  15. public class ChooseTrackedObjectMenu : MonoBehaviour
  16. {
  17. /// <summary>
  18. /// Prefab object for the buttons you press to choose the anchor.
  19. /// By default, it's Prefabs/ChooseTrackedObjectButton.
  20. /// </summary>
  21. [Tooltip("Prefab object for the buttons you press to choose the anchor. " +
  22. "By default, it's Prefabs/ChooseTrackedObjectButton.")]
  23. public GameObject chooseObjectButtonPrefab;
  24. public bool updateTrackedObjects = true;
  25. public float updateIntervalSeconds = 1f;
  26. /// <summary>
  27. /// How wide the grid of tracked object buttons can be. Objects are stacked upwards.
  28. /// </summary>
  29. [Tooltip("How wide the grid of tracked object buttons can be. Objects are stacked upwards. ")]
  30. [Space(5)]
  31. [Header("Grid")]
  32. public int maxColumns = 3;
  33. /// <summary>
  34. /// Width of the button prefab object. Used for spacing them in the grid horizontally.
  35. /// </summary>
  36. [Tooltip("Width of the button prefab object. Used for spacing them in the grid horizontally. ")]
  37. public float objectWidth = 0.3f;
  38. /// <summary>
  39. /// Width of the button prefab object. Used for spacing them in the grid vertically.
  40. /// </summary>
  41. [Tooltip("Width of the button prefab object. Used for spacing them in the grid vertically. ")]
  42. public float objectHeight = 0.4f;
  43. /// <summary>
  44. /// All button objects instantiated at runtime.
  45. /// </summary>
  46. private List<ChooseTrackedObjectButton> allButtons = new List<ChooseTrackedObjectButton>();
  47. /// <summary>
  48. /// Temporary left controller object that's created at spawn so that both left and right controllers
  49. /// can be used to choose an anchor. Destroyed once an anchor is chosen.
  50. /// </summary>
  51. [Space(5)]
  52. [Header("Anchors")]
  53. [Tooltip("Temporary left controller object that's created at spawn so that both left and right controllers " +
  54. "can be used to choose an anchor. Destroyed once an anchor is chosen. ")]
  55. public ZEDControllerTracker tempLeftController;
  56. /// <summary>
  57. /// The scene's CameraAnchor object, which holds the ZED camera. Should be disabled at start.
  58. /// </summary>
  59. [Tooltip("The scene's CameraAnchor object, which holds the ZED camera. Should be disabled at start. ")]
  60. public CameraAnchor zedAnchor;
  61. /// <summary>
  62. /// The scene's left controller. Should be disabled at start, since the left controller has the menu by default,
  63. /// which doesn't do anything until you've chosen an anchor.
  64. /// </summary>
  65. [Tooltip("The scene's left controller. Should be disabled at start, since the left controller has the menu by default, " +
  66. "which doesn't do anything until you've chosen an anchor. ")]
  67. public ZEDControllerTracker leftController;
  68. /// <summary>
  69. /// The scene's right controller. By default, this can interact with buttons/grabbables, so it's always enabled
  70. /// unless the right controller is set to be the ZED's anchor.
  71. /// </summary>
  72. [Tooltip("The scene's right controller. By default, this can interact with buttons/grabbables, so it's always enabled " +
  73. "unless the right controller is set to be the ZED's anchor. ")]
  74. public ZEDControllerTracker rightController;
  75. /// <summary>
  76. /// The "belly" menu controller, which provides the menu normally on the left (or right) hands when a Tracker is
  77. /// the ZED's anchor. Disabled by default.
  78. /// </summary>
  79. [Tooltip("The 'belly' menu controller, which provides the menu normally on the left (or right) hands when a Tracker is " +
  80. "the ZED's anchor. Disabled by default. ")]
  81. public BellyMenu bellyMenu;
  82. /// <summary>
  83. /// The transform holding the 2D floating view screen object. Disabled at start, enabled once the user chooses an anchor.
  84. /// </summary>
  85. [Tooltip("The transform holding the 2D floating view screen object. Disabled at start, enabled once the user chooses an anchor. ")]
  86. public GameObject viewScreen;
  87. /// <summary>
  88. /// Transform holding the 3D textual instructions for choosing an anchor. Used to move it above the procedural grid of buttons.
  89. /// </summary>
  90. [Space(5)]
  91. [Tooltip("Transform holding the 3D textual instructions for choosing an anchor. Used to move it above the procedural grid of buttons. ")]
  92. public Transform instructionsText;
  93. public float textBottomMargin = 0.2f;
  94. #if ZED_STEAM_VR
  95. /// <summary>
  96. /// Action we'll invoke when a controller is connected/disconnected. Cached as it's a lambda function, and we need to
  97. /// remove that listener in OnDestroy().
  98. /// </summary>
  99. private UnityAction<int, bool> deviceConnectedAction;
  100. #endif
  101. #if ZED_OCULUS
  102. public const int TOUCH_INDEX_LEFT = 0;
  103. public const int TOUCH_INDEX_RIGHT = 1;
  104. #endif
  105. // Use this for initialization
  106. void Start()
  107. {
  108. FindTrackedObjects(); //TODO: Make this happen in Update() at regular intervals, and clean up existing devices.
  109. MessageDisplay.DisplayMessageAll("Which object is holding the ZED?");
  110. #if ZED_STEAM_VR
  111. //Subscribe to controllers being connected/disconnected so we can update the available devices.
  112. deviceConnectedAction = (x, y) => FindTrackedObjects();
  113. SteamVR_Events.DeviceConnected.AddListener(deviceConnectedAction);
  114. #endif
  115. }
  116. /// <summary>
  117. /// Sets the chosen device as the anchor object, and enables/disables scene objects appropriately.
  118. /// Called by ChooseTrackedObjectMenu when the user clicks it.
  119. /// </summary>
  120. /// <param name="deviceindex">Index of the tracked object.</param>
  121. private void OnTrackedObjectSelected(int deviceindex)
  122. {
  123. //Make sure it's valid.
  124. ZEDControllerTracker.Devices trackeddevice = ZEDControllerTracker.Devices.Hmd; //Can't be HMD at end - will catch.
  125. #if ZED_STEAM_VR
  126. ETrackedDeviceClass dclass = OpenVR.System.GetTrackedDeviceClass((uint)deviceindex);
  127. if (dclass == ETrackedDeviceClass.GenericTracker) trackeddevice = ZEDControllerTracker.Devices.ViveTracker;
  128. else if (dclass == ETrackedDeviceClass.Controller)
  129. {
  130. ETrackedControllerRole role = OpenVR.System.GetControllerRoleForTrackedDeviceIndex((uint)deviceindex);
  131. if (role == ETrackedControllerRole.LeftHand) trackeddevice = ZEDControllerTracker.Devices.LeftController;
  132. else if (role == ETrackedControllerRole.RightHand) trackeddevice = ZEDControllerTracker.Devices.RightController;
  133. else
  134. {
  135. Debug.Log("Couldn't assign to device of index " + deviceindex + " as it's a controller with an unknown role.");
  136. }
  137. //TODO: Make sure this works for a third controller.
  138. }
  139. #endif
  140. #if ZED_OCULUS
  141. if(deviceindex == TOUCH_INDEX_LEFT)
  142. {
  143. trackeddevice = ZEDControllerTracker.Devices.LeftController;
  144. }
  145. else if (deviceindex == TOUCH_INDEX_RIGHT)
  146. {
  147. trackeddevice = ZEDControllerTracker.Devices.RightController;
  148. }
  149. #endif
  150. //Set up the anchor object, and readjust controllers if needed.
  151. zedAnchor.gameObject.SetActive(true);
  152. zedAnchor.controllerTracker.deviceToTrack = trackeddevice;
  153. #if ZED_STEAM_VR
  154. leftController.index = tempLeftController.index;
  155. #endif
  156. switch (trackeddevice)
  157. {
  158. #if ZED_STEAM_VR
  159. case ZEDControllerTracker.Devices.ViveTracker:
  160. leftController.index = tempLeftController.index;
  161. leftController.gameObject.SetActive(true);
  162. break;
  163. #endif
  164. case ZEDControllerTracker.Devices.LeftController:
  165. bellyMenu.gameObject.SetActive(true);
  166. break;
  167. case ZEDControllerTracker.Devices.RightController:
  168. PrimaryHandSwitcher switcher = leftController.GetComponentInChildren<PrimaryHandSwitcher>(true);
  169. if (!switcher) break;
  170. ToggleGroup3D handtoggle = switcher.transform.GetComponentInChildren<ToggleGroup3D>(true);
  171. handtoggle.toggleAtStart = false;
  172. leftController.gameObject.SetActive(true);
  173. switcher.SetPrimaryHand(false); //Switch to being left-handed.
  174. Destroy(rightController.gameObject);
  175. rightController = null;
  176. bellyMenu.gameObject.SetActive(true);
  177. break;
  178. case ZEDControllerTracker.Devices.Hmd:
  179. default:
  180. Debug.LogError("trackeddevice value somehow not set to valid value - must be a controller or tracker. ");
  181. break;
  182. }
  183. if (viewScreen) viewScreen.SetActive(true);
  184. Destroy(tempLeftController.gameObject);
  185. tempLeftController = null;
  186. //We're all done with this menu, so destroy it all.
  187. Destroy(gameObject);
  188. }
  189. private void OnDestroy()
  190. {
  191. #if ZED_STEAM_VR
  192. SteamVR_Events.DeviceConnected.RemoveListener(deviceConnectedAction);
  193. #endif
  194. }
  195. /// <summary>
  196. /// Detects all tracked objects that could potentially be the ZED's anchor, and makes a button for each.
  197. /// </summary>
  198. public void FindTrackedObjects()
  199. {
  200. //If there are any existing buttons, destroy them all.
  201. foreach(ChooseTrackedObjectButton oldbutton in allButtons)
  202. {
  203. Destroy(oldbutton.gameObject);
  204. }
  205. allButtons.Clear();
  206. List<ChooseTrackedObjectButton> newbuttons = new List<ChooseTrackedObjectButton>();
  207. #if ZED_STEAM_VR
  208. for (uint i = 0; i < 14; i++)
  209. {
  210. var error = ETrackedPropertyError.TrackedProp_Success;
  211. var chsnresult = new System.Text.StringBuilder((int)64);
  212. OpenVR.System.GetStringTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_ControllerType_String, chsnresult, 64, ref error);
  213. if (error == ETrackedPropertyError.TrackedProp_Success && chsnresult.ToString() != "")
  214. {
  215. //Create tracked object button.
  216. ChooseTrackedObjectButton button;
  217. CreateTrackedObjectPrefab(i, out button);
  218. if (button != null)
  219. {
  220. newbuttons.Add(button);
  221. }
  222. }
  223. }
  224. //allButtons.Clear();
  225. foreach (ChooseTrackedObjectButton button in newbuttons)
  226. {
  227. allButtons.Add(button);
  228. }
  229. #elif ZED_OCULUS
  230. /*//Warn the user if they don't have both controllers connected, because they need one to hold the ZED
  231. //and another to calibrate.
  232. OVRInput.Controller controllers = OVRInput.GetConnectedControllers();
  233. if (controllers != OVRInput.Controller.Touch)
  234. {
  235. Debug.Log("Warning: You need at least two controllers connected to use the calibration app: One to hold " +
  236. "the ZED and another to interact with the app.");
  237. }*/
  238. //Make a left controller button.
  239. ChooseTrackedObjectButton leftButton;
  240. CreateTrackedObjectPrefab(TOUCH_INDEX_LEFT, out leftButton);
  241. if (leftButton != null) allButtons.Add(leftButton);
  242. //Make a right controller button.
  243. ChooseTrackedObjectButton rightButton;
  244. CreateTrackedObjectPrefab(TOUCH_INDEX_RIGHT, out rightButton);
  245. if (rightButton != null) allButtons.Add(rightButton);
  246. #endif
  247. ArrangeIntoGrid(allButtons, objectWidth, objectHeight, maxColumns);
  248. }
  249. /// <summary>
  250. /// Creates a button for a tracked object detected in FindTrackedObjects() and sets it up.
  251. /// </summary>
  252. /// <param name="deviceindex">Index of the tracked object.</param>
  253. /// <param name="scriptref">Required ChooseTrackedObjectButton script that must be on the prefab.</param>
  254. private GameObject CreateTrackedObjectPrefab(uint deviceindex, out ChooseTrackedObjectButton scriptref)
  255. {
  256. string label = "ERROR";
  257. #if ZED_STEAM_VR
  258. ETrackedDeviceClass dclass = OpenVR.System.GetTrackedDeviceClass(deviceindex);
  259. ETrackedControllerRole role = OpenVR.System.GetControllerRoleForTrackedDeviceIndex(deviceindex);
  260. if (!(dclass == ETrackedDeviceClass.Controller || dclass == ETrackedDeviceClass.GenericTracker))
  261. {
  262. //Debug.LogError("Tried to create button for tracked device of index " + deviceindex + ", but it's role is " + dclass + ".");
  263. scriptref = null;
  264. return null;
  265. }
  266. if (role == ETrackedControllerRole.LeftHand)
  267. {
  268. label = "Left\r\nController";
  269. }
  270. else if (role == ETrackedControllerRole.RightHand)
  271. {
  272. label = "Right\r\nController";
  273. }
  274. else if (dclass == ETrackedDeviceClass.GenericTracker)
  275. {
  276. label = "Tracker";
  277. }
  278. else
  279. {
  280. //Debug.LogError("Tried to create button for tracked device of index " + deviceindex + " with an invalid role/device class combo: " +
  281. // role + " / " + dclass);
  282. scriptref = null;
  283. return null;
  284. }
  285. #elif ZED_OCULUS
  286. if (deviceindex == TOUCH_INDEX_LEFT)
  287. {
  288. label = "Left\r\nController";
  289. }
  290. else if (deviceindex == TOUCH_INDEX_RIGHT)
  291. {
  292. label = "Right\r\nController";
  293. }
  294. #endif
  295. GameObject buttongo = Instantiate(chooseObjectButtonPrefab, transform, false);
  296. scriptref = buttongo.GetComponentInChildren<ChooseTrackedObjectButton>();
  297. scriptref.SetDeviceIndex((int)deviceindex);
  298. scriptref.SetLabel(label);
  299. buttongo.name = "Select " + label + " Button";
  300. scriptref.OnTrackedObjectSelected += OnTrackedObjectSelected;
  301. return buttongo;
  302. }
  303. /// <summary>
  304. /// Creates a button for a tracked object detected in FindTrackedObjects() and sets it up.
  305. /// Overload that doesn't require you to output a ChooseTrackedObjectButton reference.
  306. /// </summary>
  307. /// <param name="deviceindex">Index of the tracked object.</param>
  308. private GameObject CreateTrackedObjectPrefab(uint deviceindex)
  309. {
  310. ChooseTrackedObjectButton throwaway;
  311. return CreateTrackedObjectPrefab(deviceindex, out throwaway);
  312. }
  313. /// <summary>
  314. /// Arranges the anchor buttons into a grid, spaced based on the total number and user preferences.
  315. /// Also adjusts the text object (instructionsText) to be above the grid.
  316. /// </summary>
  317. /// <param name="buttonlist">List of all instantiated buttons</param>
  318. /// <param name="objwidth">Width of each prefab, used for spacing.</param>
  319. /// <param name="objheight">Height of each prefab, used for spacing.</param>
  320. /// <param name="maxcolumns">How wide the grid can be before it stacks to a new row.</param>
  321. private void ArrangeIntoGrid(List<ChooseTrackedObjectButton> buttonlist, float objwidth, float objheight, int maxcolumns)
  322. {
  323. int numrows = Mathf.CeilToInt(buttonlist.Count / (float)maxcolumns);
  324. int remainingobjects = buttonlist.Count;
  325. float ycenterpoint = numrows / 2f + 0.5f;
  326. ycenterpoint -= 1;
  327. for (int v = 0; v < numrows; v++)
  328. {
  329. //Row height info.
  330. float yoffset = v - ycenterpoint;
  331. yoffset *= objheight;
  332. int thisrowwidth = (maxcolumns < remainingobjects) ? maxcolumns : remainingobjects;
  333. float xcenterpoint = thisrowwidth / 2f + 0.5f; //If you drew a line in the middle of a row of objects, where would it fall?
  334. xcenterpoint -= 1; //To account for index starting at 0. (yes we could just subtract 0.5 instead of adding but that's less clear.)
  335. for (int u = 0; u < thisrowwidth; u++)
  336. {
  337. int goindex = v * maxcolumns + u;
  338. float xoffset = u - xcenterpoint;
  339. xoffset *= objwidth;
  340. //TODO: Y offset
  341. Vector3 oldpos = buttonlist[goindex].transform.localPosition;
  342. buttonlist[goindex].transform.localPosition = new Vector3(xoffset, yoffset, oldpos.z);
  343. }
  344. remainingobjects -= thisrowwidth; //To simplify process of deciding how long the rows should be.
  345. }
  346. //Reposition the instruction text to be just above the grid.
  347. instructionsText.localPosition = new Vector3(instructionsText.localPosition.x, numrows / 2f * objheight + textBottomMargin, instructionsText.localPosition.z);
  348. }
  349. }