123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Events;
- #if ZED_STEAM_VR
- using Valve.VR;
- #endif
- /// <summary>
- /// Lets the user choose the tracked object that the ZED is anchored to, for the MR calibration scene.
- /// Works by spawning a prefab button for each tracked object, complete with graphics, and arranging them
- /// into a grid. Each is assigned a callback that will enable the scene's CameraAnchor object and set its
- /// tracked object accordingly.
- /// </summary>
- public class ChooseTrackedObjectMenu : MonoBehaviour
- {
- /// <summary>
- /// Prefab object for the buttons you press to choose the anchor.
- /// By default, it's Prefabs/ChooseTrackedObjectButton.
- /// </summary>
- [Tooltip("Prefab object for the buttons you press to choose the anchor. " +
- "By default, it's Prefabs/ChooseTrackedObjectButton.")]
- public GameObject chooseObjectButtonPrefab;
- public bool updateTrackedObjects = true;
- public float updateIntervalSeconds = 1f;
- /// <summary>
- /// How wide the grid of tracked object buttons can be. Objects are stacked upwards.
- /// </summary>
- [Tooltip("How wide the grid of tracked object buttons can be. Objects are stacked upwards. ")]
- [Space(5)]
- [Header("Grid")]
- public int maxColumns = 3;
- /// <summary>
- /// Width of the button prefab object. Used for spacing them in the grid horizontally.
- /// </summary>
- [Tooltip("Width of the button prefab object. Used for spacing them in the grid horizontally. ")]
- public float objectWidth = 0.3f;
- /// <summary>
- /// Width of the button prefab object. Used for spacing them in the grid vertically.
- /// </summary>
- [Tooltip("Width of the button prefab object. Used for spacing them in the grid vertically. ")]
- public float objectHeight = 0.4f;
- /// <summary>
- /// All button objects instantiated at runtime.
- /// </summary>
- private List<ChooseTrackedObjectButton> allButtons = new List<ChooseTrackedObjectButton>();
- /// <summary>
- /// Temporary left controller object that's created at spawn so that both left and right controllers
- /// can be used to choose an anchor. Destroyed once an anchor is chosen.
- /// </summary>
- [Space(5)]
- [Header("Anchors")]
- [Tooltip("Temporary left controller object that's created at spawn so that both left and right controllers " +
- "can be used to choose an anchor. Destroyed once an anchor is chosen. ")]
- public ZEDControllerTracker tempLeftController;
- /// <summary>
- /// The scene's CameraAnchor object, which holds the ZED camera. Should be disabled at start.
- /// </summary>
- [Tooltip("The scene's CameraAnchor object, which holds the ZED camera. Should be disabled at start. ")]
- public CameraAnchor zedAnchor;
- /// <summary>
- /// The scene's left controller. Should be disabled at start, since the left controller has the menu by default,
- /// which doesn't do anything until you've chosen an anchor.
- /// </summary>
- [Tooltip("The scene's left controller. Should be disabled at start, since the left controller has the menu by default, " +
- "which doesn't do anything until you've chosen an anchor. ")]
- public ZEDControllerTracker leftController;
- /// <summary>
- /// The scene's right controller. By default, this can interact with buttons/grabbables, so it's always enabled
- /// unless the right controller is set to be the ZED's anchor.
- /// </summary>
- [Tooltip("The scene's right controller. By default, this can interact with buttons/grabbables, so it's always enabled " +
- "unless the right controller is set to be the ZED's anchor. ")]
- public ZEDControllerTracker rightController;
- /// <summary>
- /// The "belly" menu controller, which provides the menu normally on the left (or right) hands when a Tracker is
- /// the ZED's anchor. Disabled by default.
- /// </summary>
- [Tooltip("The 'belly' menu controller, which provides the menu normally on the left (or right) hands when a Tracker is " +
- "the ZED's anchor. Disabled by default. ")]
- public BellyMenu bellyMenu;
- /// <summary>
- /// The transform holding the 2D floating view screen object. Disabled at start, enabled once the user chooses an anchor.
- /// </summary>
- [Tooltip("The transform holding the 2D floating view screen object. Disabled at start, enabled once the user chooses an anchor. ")]
- public GameObject viewScreen;
- /// <summary>
- /// Transform holding the 3D textual instructions for choosing an anchor. Used to move it above the procedural grid of buttons.
- /// </summary>
- [Space(5)]
- [Tooltip("Transform holding the 3D textual instructions for choosing an anchor. Used to move it above the procedural grid of buttons. ")]
- public Transform instructionsText;
- public float textBottomMargin = 0.2f;
- #if ZED_STEAM_VR
- /// <summary>
- /// Action we'll invoke when a controller is connected/disconnected. Cached as it's a lambda function, and we need to
- /// remove that listener in OnDestroy().
- /// </summary>
- private UnityAction<int, bool> deviceConnectedAction;
- #endif
- #if ZED_OCULUS
- public const int TOUCH_INDEX_LEFT = 0;
- public const int TOUCH_INDEX_RIGHT = 1;
- #endif
- // Use this for initialization
- void Start()
- {
- FindTrackedObjects(); //TODO: Make this happen in Update() at regular intervals, and clean up existing devices.
- MessageDisplay.DisplayMessageAll("Which object is holding the ZED?");
- #if ZED_STEAM_VR
- //Subscribe to controllers being connected/disconnected so we can update the available devices.
- deviceConnectedAction = (x, y) => FindTrackedObjects();
- SteamVR_Events.DeviceConnected.AddListener(deviceConnectedAction);
- #endif
- }
- /// <summary>
- /// Sets the chosen device as the anchor object, and enables/disables scene objects appropriately.
- /// Called by ChooseTrackedObjectMenu when the user clicks it.
- /// </summary>
- /// <param name="deviceindex">Index of the tracked object.</param>
- private void OnTrackedObjectSelected(int deviceindex)
- {
- //Make sure it's valid.
- ZEDControllerTracker.Devices trackeddevice = ZEDControllerTracker.Devices.Hmd; //Can't be HMD at end - will catch.
- #if ZED_STEAM_VR
- ETrackedDeviceClass dclass = OpenVR.System.GetTrackedDeviceClass((uint)deviceindex);
- if (dclass == ETrackedDeviceClass.GenericTracker) trackeddevice = ZEDControllerTracker.Devices.ViveTracker;
- else if (dclass == ETrackedDeviceClass.Controller)
- {
- ETrackedControllerRole role = OpenVR.System.GetControllerRoleForTrackedDeviceIndex((uint)deviceindex);
- if (role == ETrackedControllerRole.LeftHand) trackeddevice = ZEDControllerTracker.Devices.LeftController;
- else if (role == ETrackedControllerRole.RightHand) trackeddevice = ZEDControllerTracker.Devices.RightController;
- else
- {
- Debug.Log("Couldn't assign to device of index " + deviceindex + " as it's a controller with an unknown role.");
- }
- //TODO: Make sure this works for a third controller.
- }
- #endif
- #if ZED_OCULUS
- if(deviceindex == TOUCH_INDEX_LEFT)
- {
- trackeddevice = ZEDControllerTracker.Devices.LeftController;
- }
- else if (deviceindex == TOUCH_INDEX_RIGHT)
- {
- trackeddevice = ZEDControllerTracker.Devices.RightController;
- }
- #endif
- //Set up the anchor object, and readjust controllers if needed.
- zedAnchor.gameObject.SetActive(true);
- zedAnchor.controllerTracker.deviceToTrack = trackeddevice;
- #if ZED_STEAM_VR
- leftController.index = tempLeftController.index;
- #endif
- switch (trackeddevice)
- {
- #if ZED_STEAM_VR
- case ZEDControllerTracker.Devices.ViveTracker:
- leftController.index = tempLeftController.index;
- leftController.gameObject.SetActive(true);
- break;
- #endif
- case ZEDControllerTracker.Devices.LeftController:
- bellyMenu.gameObject.SetActive(true);
- break;
- case ZEDControllerTracker.Devices.RightController:
- PrimaryHandSwitcher switcher = leftController.GetComponentInChildren<PrimaryHandSwitcher>(true);
- if (!switcher) break;
- ToggleGroup3D handtoggle = switcher.transform.GetComponentInChildren<ToggleGroup3D>(true);
- handtoggle.toggleAtStart = false;
- leftController.gameObject.SetActive(true);
- switcher.SetPrimaryHand(false); //Switch to being left-handed.
- Destroy(rightController.gameObject);
- rightController = null;
- bellyMenu.gameObject.SetActive(true);
- break;
- case ZEDControllerTracker.Devices.Hmd:
- default:
- Debug.LogError("trackeddevice value somehow not set to valid value - must be a controller or tracker. ");
- break;
- }
- if (viewScreen) viewScreen.SetActive(true);
- Destroy(tempLeftController.gameObject);
- tempLeftController = null;
- //We're all done with this menu, so destroy it all.
- Destroy(gameObject);
- }
- private void OnDestroy()
- {
- #if ZED_STEAM_VR
- SteamVR_Events.DeviceConnected.RemoveListener(deviceConnectedAction);
- #endif
- }
- /// <summary>
- /// Detects all tracked objects that could potentially be the ZED's anchor, and makes a button for each.
- /// </summary>
- public void FindTrackedObjects()
- {
- //If there are any existing buttons, destroy them all.
- foreach(ChooseTrackedObjectButton oldbutton in allButtons)
- {
- Destroy(oldbutton.gameObject);
- }
- allButtons.Clear();
- List<ChooseTrackedObjectButton> newbuttons = new List<ChooseTrackedObjectButton>();
- #if ZED_STEAM_VR
- for (uint i = 0; i < 14; i++)
- {
- var error = ETrackedPropertyError.TrackedProp_Success;
- var chsnresult = new System.Text.StringBuilder((int)64);
- OpenVR.System.GetStringTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_ControllerType_String, chsnresult, 64, ref error);
- if (error == ETrackedPropertyError.TrackedProp_Success && chsnresult.ToString() != "")
- {
- //Create tracked object button.
- ChooseTrackedObjectButton button;
- CreateTrackedObjectPrefab(i, out button);
- if (button != null)
- {
- newbuttons.Add(button);
- }
- }
- }
- //allButtons.Clear();
- foreach (ChooseTrackedObjectButton button in newbuttons)
- {
- allButtons.Add(button);
- }
- #elif ZED_OCULUS
- /*//Warn the user if they don't have both controllers connected, because they need one to hold the ZED
- //and another to calibrate.
- OVRInput.Controller controllers = OVRInput.GetConnectedControllers();
- if (controllers != OVRInput.Controller.Touch)
- {
- Debug.Log("Warning: You need at least two controllers connected to use the calibration app: One to hold " +
- "the ZED and another to interact with the app.");
- }*/
- //Make a left controller button.
- ChooseTrackedObjectButton leftButton;
- CreateTrackedObjectPrefab(TOUCH_INDEX_LEFT, out leftButton);
- if (leftButton != null) allButtons.Add(leftButton);
- //Make a right controller button.
- ChooseTrackedObjectButton rightButton;
- CreateTrackedObjectPrefab(TOUCH_INDEX_RIGHT, out rightButton);
- if (rightButton != null) allButtons.Add(rightButton);
- #endif
- ArrangeIntoGrid(allButtons, objectWidth, objectHeight, maxColumns);
- }
- /// <summary>
- /// Creates a button for a tracked object detected in FindTrackedObjects() and sets it up.
- /// </summary>
- /// <param name="deviceindex">Index of the tracked object.</param>
- /// <param name="scriptref">Required ChooseTrackedObjectButton script that must be on the prefab.</param>
- private GameObject CreateTrackedObjectPrefab(uint deviceindex, out ChooseTrackedObjectButton scriptref)
- {
- string label = "ERROR";
- #if ZED_STEAM_VR
- ETrackedDeviceClass dclass = OpenVR.System.GetTrackedDeviceClass(deviceindex);
- ETrackedControllerRole role = OpenVR.System.GetControllerRoleForTrackedDeviceIndex(deviceindex);
- if (!(dclass == ETrackedDeviceClass.Controller || dclass == ETrackedDeviceClass.GenericTracker))
- {
- //Debug.LogError("Tried to create button for tracked device of index " + deviceindex + ", but it's role is " + dclass + ".");
- scriptref = null;
- return null;
- }
- if (role == ETrackedControllerRole.LeftHand)
- {
- label = "Left\r\nController";
- }
- else if (role == ETrackedControllerRole.RightHand)
- {
- label = "Right\r\nController";
- }
- else if (dclass == ETrackedDeviceClass.GenericTracker)
- {
- label = "Tracker";
- }
- else
- {
- //Debug.LogError("Tried to create button for tracked device of index " + deviceindex + " with an invalid role/device class combo: " +
- // role + " / " + dclass);
- scriptref = null;
- return null;
- }
- #elif ZED_OCULUS
- if (deviceindex == TOUCH_INDEX_LEFT)
- {
- label = "Left\r\nController";
- }
- else if (deviceindex == TOUCH_INDEX_RIGHT)
- {
- label = "Right\r\nController";
- }
- #endif
- GameObject buttongo = Instantiate(chooseObjectButtonPrefab, transform, false);
- scriptref = buttongo.GetComponentInChildren<ChooseTrackedObjectButton>();
- scriptref.SetDeviceIndex((int)deviceindex);
- scriptref.SetLabel(label);
- buttongo.name = "Select " + label + " Button";
- scriptref.OnTrackedObjectSelected += OnTrackedObjectSelected;
- return buttongo;
- }
- /// <summary>
- /// Creates a button for a tracked object detected in FindTrackedObjects() and sets it up.
- /// Overload that doesn't require you to output a ChooseTrackedObjectButton reference.
- /// </summary>
- /// <param name="deviceindex">Index of the tracked object.</param>
- private GameObject CreateTrackedObjectPrefab(uint deviceindex)
- {
- ChooseTrackedObjectButton throwaway;
- return CreateTrackedObjectPrefab(deviceindex, out throwaway);
- }
- /// <summary>
- /// Arranges the anchor buttons into a grid, spaced based on the total number and user preferences.
- /// Also adjusts the text object (instructionsText) to be above the grid.
- /// </summary>
- /// <param name="buttonlist">List of all instantiated buttons</param>
- /// <param name="objwidth">Width of each prefab, used for spacing.</param>
- /// <param name="objheight">Height of each prefab, used for spacing.</param>
- /// <param name="maxcolumns">How wide the grid can be before it stacks to a new row.</param>
- private void ArrangeIntoGrid(List<ChooseTrackedObjectButton> buttonlist, float objwidth, float objheight, int maxcolumns)
- {
- int numrows = Mathf.CeilToInt(buttonlist.Count / (float)maxcolumns);
- int remainingobjects = buttonlist.Count;
- float ycenterpoint = numrows / 2f + 0.5f;
- ycenterpoint -= 1;
- for (int v = 0; v < numrows; v++)
- {
- //Row height info.
- float yoffset = v - ycenterpoint;
- yoffset *= objheight;
- int thisrowwidth = (maxcolumns < remainingobjects) ? maxcolumns : remainingobjects;
- float xcenterpoint = thisrowwidth / 2f + 0.5f; //If you drew a line in the middle of a row of objects, where would it fall?
- xcenterpoint -= 1; //To account for index starting at 0. (yes we could just subtract 0.5 instead of adding but that's less clear.)
- for (int u = 0; u < thisrowwidth; u++)
- {
- int goindex = v * maxcolumns + u;
- float xoffset = u - xcenterpoint;
- xoffset *= objwidth;
- //TODO: Y offset
- Vector3 oldpos = buttonlist[goindex].transform.localPosition;
- buttonlist[goindex].transform.localPosition = new Vector3(xoffset, yoffset, oldpos.z);
- }
- remainingobjects -= thisrowwidth; //To simplify process of deciding how long the rows should be.
- }
- //Reposition the instruction text to be just above the grid.
- instructionsText.localPosition = new Vector3(instructionsText.localPosition.x, numrows / 2f * objheight + textBottomMargin, instructionsText.localPosition.z);
- }
- }
|