VRInput.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. //#define OCULUS_SDK //<-- define this in your project settings if you have the OVR API
  2. #if UNITY_2017_2_OR_NEWER
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. using UnityEngine;
  7. using UnityEngine.XR;
  8. using ZenFulcrum.VR.OpenVRBinding;
  9. namespace ZenFulcrum.EmbeddedBrowser.VR {
  10. public enum InputAxis {
  11. MainTrigger,//main index finger trigger
  12. Grip,//Vive squeeze/Oculus hand trigger
  13. JoypadX, JoypadY,//touchpad/joystick x/y position
  14. Joypad,//touchpad/joystick click/press
  15. Application,//application meanu/start button
  16. }
  17. public enum JoyPadType {
  18. None,
  19. Joystick,
  20. TouchPad,
  21. }
  22. /// <summary>
  23. /// Unity's VR input system is sorely lacking in usefulness.
  24. /// We can finally get pose data in a more-or-less straightforward manner (InputTracking.GetNodeStates,
  25. /// state.TryGetPosition, etc.), but getting the axis and buttons is an awful mess.
  26. ///
  27. /// This page https://docs.unity3d.com/Manual/OpenVRControllers.html says we can access the inputs via
  28. /// the input system! And, you can look at (of all things) the Joystick names to see which joystick
  29. /// is a VR controller at runtime. But guess what? There's no Unity API to fetch the value of a given
  30. /// axis on a given controller! Extra-stupid, right? You can define a specific input in the input menu,
  31. /// but that doesn't help when you switch to a different computer and the joystick numbers change!
  32. /// Short of defining an "input" for every possible controller * every axis you use, there's not a
  33. /// way to get the controller axis inputs in a way that will work reliably across different machines.
  34. ///
  35. /// (We can fetch buttons manually via Input.GetKey(KeyCode.Joystick1Button0 + buttonId + joystickIdx * 20),
  36. /// but this don't address the axis issue at all.)
  37. ///
  38. /// Anyway, this is a workaround. Around another Unity problem. Implemented by hooking into backend APIs directly.
  39. /// </summary>
  40. public class VRInput {
  41. private static VRInput impl;
  42. public static void Init() {
  43. if (impl == null) impl = GetImpl();
  44. }
  45. /// <summary>
  46. /// Returns the state of the given button/axis.
  47. /// </summary>
  48. /// <param name="node"></param>
  49. /// <param name="axis"></param>
  50. /// <returns></returns>
  51. public static float GetAxis(XRNodeState node, InputAxis axis) {
  52. if (impl == null) impl = GetImpl();
  53. return impl.GetAxisValue(node, axis);
  54. }
  55. /// <summary>
  56. /// If the controller is capable, returns if (and sometimes how closely) the player is touching
  57. /// the given control.
  58. /// </summary>
  59. /// <param name="node"></param>
  60. /// <param name="axis"></param>
  61. /// <returns></returns>
  62. public static float GetTouch(XRNodeState node, InputAxis axis) {
  63. if (impl == null) impl = GetImpl();
  64. return impl.GetTouchValue(node, axis);
  65. }
  66. protected virtual float GetAxisValue(XRNodeState node, InputAxis axis) {
  67. return 0;
  68. }
  69. protected virtual float GetTouchValue(XRNodeState node, InputAxis axis) {
  70. return 0;
  71. }
  72. private static Dictionary<ulong, JoyPadType> nodeTypes = new Dictionary<ulong, JoyPadType>();
  73. public static JoyPadType GetJoypadType(XRNodeState node) {
  74. JoyPadType ret;
  75. if (!nodeTypes.TryGetValue(node.uniqueID, out ret)) {
  76. ret = JoyPadType.None;
  77. if (impl == null) impl = GetImpl();
  78. var name = impl.GetNodeName(node);
  79. if (name.Contains("Oculus Touch Controller") || name.StartsWith("Oculus Rift CV1")) {
  80. //OpenVR gives us "Oculus Rift CV1 (Left Controller)" etc. where I wish it would mention the type of controller (Touch)
  81. ret = JoyPadType.Joystick;
  82. } else if (name.StartsWith("Vive Controller")) {
  83. ret = JoyPadType.TouchPad;
  84. } else {
  85. Debug.LogWarning("Unknown controller type: " + name);
  86. }
  87. nodeTypes[node.uniqueID] = ret;
  88. }
  89. return ret;
  90. }
  91. public virtual string GetNodeName(XRNodeState node) {
  92. return InputTracking.GetNodeName(node.uniqueID);
  93. }
  94. // public virtual JoyPadType JoypadTypeValue(XRNodeState node) { return JoyPadType.None; }
  95. private static VRInput GetImpl() {
  96. if (XRSettings.loadedDeviceName == "OpenVR") {
  97. return new OpenVRInput();
  98. } else if (XRSettings.loadedDeviceName == "Oculus") {
  99. #if OCULUS_SDK
  100. return new OculusVRInput();
  101. #else
  102. Debug.LogError("To use the Oculus API for input, import the Oculus SDK and define OCULUS_SDK");
  103. return new VRInput();
  104. #endif
  105. } else {
  106. Debug.LogError("Unknown VR input system: " + XRSettings.loadedDeviceName);
  107. return new VRInput();
  108. }
  109. }
  110. }
  111. class OpenVRInput : VRInput {
  112. protected VRControllerState_t lastState;
  113. public static string GetStringProperty(uint deviceId, ETrackedDeviceProperty prop) {
  114. var buffer = new StringBuilder((int)OpenVR.k_unMaxPropertyStringSize);
  115. ETrackedPropertyError err = ETrackedPropertyError.TrackedProp_Success;
  116. OpenVR.System.GetStringTrackedDeviceProperty(
  117. deviceId, prop,
  118. buffer, OpenVR.k_unMaxPropertyStringSize, ref err
  119. );
  120. if (err != ETrackedPropertyError.TrackedProp_Success) {
  121. throw new Exception("Failed to get property " + prop + " on device " + deviceId + ": " + err);
  122. }
  123. return buffer.ToString();
  124. }
  125. public override string GetNodeName(XRNodeState node) {
  126. var deviceId = (uint)GetDeviceId(node);
  127. try {
  128. return GetStringProperty(deviceId, ETrackedDeviceProperty.Prop_ModelNumber_String);
  129. } catch (Exception ex) {
  130. Debug.LogError("Failed to get device name for device " + deviceId + ": " + ex.Message);
  131. return base.GetNodeName(node);
  132. }
  133. }
  134. protected void ReadState(XRNodeState node) {
  135. if (OpenVR.System == null) {
  136. Debug.LogWarning("OpenVR not active");
  137. lastState = default(VRControllerState_t);
  138. return;
  139. }
  140. var controllerId = GetDeviceId(node);
  141. if (controllerId < 0) {
  142. lastState = default(VRControllerState_t);
  143. return;
  144. }
  145. //Debug.Log("Id is " + controllerId);
  146. var res = OpenVR.System.GetControllerState(
  147. (uint)controllerId, ref lastState,
  148. (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VRControllerState_t))
  149. );
  150. if (!res) {
  151. Debug.LogWarning("Failed to get controller state");
  152. }
  153. }
  154. protected override float GetAxisValue(XRNodeState node, InputAxis axis) {
  155. ReadState(node);
  156. switch (axis) {
  157. case InputAxis.MainTrigger:
  158. return lastState.rAxis1.x;
  159. case InputAxis.Grip:
  160. return (lastState.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_Grip)) != 0 ? 1 : 0;
  161. case InputAxis.JoypadX:
  162. return lastState.rAxis0.x;
  163. case InputAxis.JoypadY:
  164. return lastState.rAxis0.y;
  165. case InputAxis.Joypad:
  166. return (lastState.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_SteamVR_Touchpad)) != 0 ? 1 : 0;
  167. case InputAxis.Application:
  168. return (lastState.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_ApplicationMenu)) != 0 ? 1 : 0;
  169. default:
  170. throw new ArgumentOutOfRangeException("axis", axis, null);
  171. }
  172. }
  173. protected override float GetTouchValue(XRNodeState node, InputAxis axis) {
  174. ReadState(node);
  175. switch (axis) {
  176. case InputAxis.Joypad:
  177. return (lastState.ulButtonTouched & (1ul << (int)EVRButtonId.k_EButton_SteamVR_Touchpad)) != 0 ? 1 : 0;
  178. default:
  179. return 0;
  180. }
  181. }
  182. private int GetDeviceId(XRNodeState node) {
  183. var targetRole = node.nodeType == XRNode.LeftHand ? ETrackedControllerRole.LeftHand : ETrackedControllerRole.RightHand;
  184. for (uint i = 0; i < OpenVR.k_unMaxTrackedDeviceCount; i++) {
  185. var role = OpenVR.System.GetControllerRoleForTrackedDeviceIndex(i);
  186. if (role == targetRole) return (int)i;
  187. }
  188. return -1;
  189. }
  190. }
  191. #if OCULUS_SDK
  192. class OculusVRInput : VRInput {
  193. protected override float GetAxisValue(XRNodeState node, InputAxis axis) {
  194. var controller = GetController(node);
  195. OVRPlugin.ControllerState4 state = OVRPlugin.GetControllerState4((uint)controller);
  196. switch (axis) {
  197. case InputAxis.MainTrigger:
  198. return controller == OVRInput.Controller.LTouch ? state.LIndexTrigger : state.RIndexTrigger;
  199. case InputAxis.Grip:
  200. return controller == OVRInput.Controller.LTouch ? state.LHandTrigger : state.RHandTrigger;
  201. case InputAxis.JoypadX:
  202. case InputAxis.JoypadY: {
  203. var joy = controller == OVRInput.Controller.LTouch ? state.LThumbstick : state.RThumbstick;
  204. return axis == InputAxis.JoypadX ? joy.x : joy.y;
  205. }
  206. case InputAxis.Joypad: {
  207. var buttonId = controller == OVRInput.Controller.LTouch ? 0x00000400 : 0x00000004; //see enum ovrButton_ in OVER_CAPI.h
  208. return (state.Buttons & buttonId) != 0 ? 1 : 0;
  209. }
  210. default:
  211. return 0;
  212. }
  213. }
  214. private OVRInput.Controller GetController(XRNodeState node) {
  215. switch (node.nodeType) {
  216. case XRNode.LeftHand:
  217. return OVRInput.Controller.LTouch;
  218. case XRNode.RightHand:
  219. return OVRInput.Controller.RTouch;
  220. default:
  221. return OVRInput.Controller.None;
  222. }
  223. }
  224. protected override float GetTouchValue(XRNodeState node, InputAxis axis) {
  225. //nothing touch-related is presently used for Oculus Touch controllers, so nothing is all we need here
  226. return 0;
  227. }
  228. }
  229. #endif
  230. }
  231. #endif