SteamVR_Controller.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. //======= Copyright (c) Valve Corporation, All rights reserved. ===============
  2. //
  3. // Purpose: Wrapper for working with SteamVR controller input
  4. //
  5. // Example usage:
  6. //
  7. // var deviceIndex = SteamVR_Controller.GetDeviceIndex(SteamVR_Controller.DeviceRelation.Leftmost);
  8. // if (deviceIndex != -1 && SteamVR_Controller.Input(deviceIndex).GetPressDown(SteamVR_Controller.ButtonMask.Trigger))
  9. // SteamVR_Controller.Input(deviceIndex).TriggerHapticPulse(1000);
  10. //
  11. //=============================================================================
  12. using UnityEngine;
  13. using Valve.VR;
  14. public class SteamVR_Controller
  15. {
  16. public class ButtonMask
  17. {
  18. public const ulong System = (1ul << (int)EVRButtonId.k_EButton_System); // reserved
  19. public const ulong ApplicationMenu = (1ul << (int)EVRButtonId.k_EButton_ApplicationMenu);
  20. public const ulong Grip = (1ul << (int)EVRButtonId.k_EButton_Grip);
  21. public const ulong Axis0 = (1ul << (int)EVRButtonId.k_EButton_Axis0);
  22. public const ulong Axis1 = (1ul << (int)EVRButtonId.k_EButton_Axis1);
  23. public const ulong Axis2 = (1ul << (int)EVRButtonId.k_EButton_Axis2);
  24. public const ulong Axis3 = (1ul << (int)EVRButtonId.k_EButton_Axis3);
  25. public const ulong Axis4 = (1ul << (int)EVRButtonId.k_EButton_Axis4);
  26. public const ulong Touchpad = (1ul << (int)EVRButtonId.k_EButton_SteamVR_Touchpad);
  27. public const ulong Trigger = (1ul << (int)EVRButtonId.k_EButton_SteamVR_Trigger);
  28. }
  29. public class Device
  30. {
  31. public Device(uint i) { index = i; }
  32. public uint index { get; private set; }
  33. public bool valid { get; private set; }
  34. public bool connected { get { Update(); return pose.bDeviceIsConnected; } }
  35. public bool hasTracking { get { Update(); return pose.bPoseIsValid; } }
  36. public bool outOfRange { get { Update(); return pose.eTrackingResult == ETrackingResult.Running_OutOfRange || pose.eTrackingResult == ETrackingResult.Calibrating_OutOfRange; } }
  37. public bool calibrating { get { Update(); return pose.eTrackingResult == ETrackingResult.Calibrating_InProgress || pose.eTrackingResult == ETrackingResult.Calibrating_OutOfRange; } }
  38. public bool uninitialized { get { Update(); return pose.eTrackingResult == ETrackingResult.Uninitialized; } }
  39. // These values are only accurate for the last controller state change (e.g. trigger release), and by definition, will always lag behind
  40. // the predicted visual poses that drive SteamVR_TrackedObjects since they are sync'd to the input timestamp that caused them to update.
  41. public SteamVR_Utils.RigidTransform transform { get { Update(); return new SteamVR_Utils.RigidTransform(pose.mDeviceToAbsoluteTracking); } }
  42. public Vector3 velocity { get { Update(); return new Vector3(pose.vVelocity.v0, pose.vVelocity.v1, -pose.vVelocity.v2); } }
  43. public Vector3 angularVelocity { get { Update(); return new Vector3(-pose.vAngularVelocity.v0, -pose.vAngularVelocity.v1, pose.vAngularVelocity.v2); } }
  44. public VRControllerState_t GetState() { Update(); return state; }
  45. public VRControllerState_t GetPrevState() { Update(); return prevState; }
  46. public TrackedDevicePose_t GetPose() { Update(); return pose; }
  47. VRControllerState_t state, prevState;
  48. TrackedDevicePose_t pose;
  49. int prevFrameCount = -1;
  50. public void Update()
  51. {
  52. if (Time.frameCount != prevFrameCount)
  53. {
  54. prevFrameCount = Time.frameCount;
  55. prevState = state;
  56. var system = OpenVR.System;
  57. if (system != null)
  58. {
  59. valid = system.GetControllerStateWithPose(SteamVR_Render.instance.trackingSpace, index, ref state, (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VRControllerState_t)), ref pose);
  60. UpdateHairTrigger();
  61. }
  62. }
  63. }
  64. public bool GetPress(ulong buttonMask) { Update(); return (state.ulButtonPressed & buttonMask) != 0; }
  65. public bool GetPressDown(ulong buttonMask) { Update(); return (state.ulButtonPressed & buttonMask) != 0 && (prevState.ulButtonPressed & buttonMask) == 0; }
  66. public bool GetPressUp(ulong buttonMask) { Update(); return (state.ulButtonPressed & buttonMask) == 0 && (prevState.ulButtonPressed & buttonMask) != 0; }
  67. public bool GetPress(EVRButtonId buttonId) { return GetPress(1ul << (int)buttonId); }
  68. public bool GetPressDown(EVRButtonId buttonId) { return GetPressDown(1ul << (int)buttonId); }
  69. public bool GetPressUp(EVRButtonId buttonId) { return GetPressUp(1ul << (int)buttonId); }
  70. public bool GetTouch(ulong buttonMask) { Update(); return (state.ulButtonTouched & buttonMask) != 0; }
  71. public bool GetTouchDown(ulong buttonMask) { Update(); return (state.ulButtonTouched & buttonMask) != 0 && (prevState.ulButtonTouched & buttonMask) == 0; }
  72. public bool GetTouchUp(ulong buttonMask) { Update(); return (state.ulButtonTouched & buttonMask) == 0 && (prevState.ulButtonTouched & buttonMask) != 0; }
  73. public bool GetTouch(EVRButtonId buttonId) { return GetTouch(1ul << (int)buttonId); }
  74. public bool GetTouchDown(EVRButtonId buttonId) { return GetTouchDown(1ul << (int)buttonId); }
  75. public bool GetTouchUp(EVRButtonId buttonId) { return GetTouchUp(1ul << (int)buttonId); }
  76. public Vector2 GetAxis(EVRButtonId buttonId = EVRButtonId.k_EButton_SteamVR_Touchpad)
  77. {
  78. Update();
  79. var axisId = (uint)buttonId - (uint)EVRButtonId.k_EButton_Axis0;
  80. switch (axisId)
  81. {
  82. case 0: return new Vector2(state.rAxis0.x, state.rAxis0.y);
  83. case 1: return new Vector2(state.rAxis1.x, state.rAxis1.y);
  84. case 2: return new Vector2(state.rAxis2.x, state.rAxis2.y);
  85. case 3: return new Vector2(state.rAxis3.x, state.rAxis3.y);
  86. case 4: return new Vector2(state.rAxis4.x, state.rAxis4.y);
  87. }
  88. return Vector2.zero;
  89. }
  90. public void TriggerHapticPulse(ushort durationMicroSec = 500, EVRButtonId buttonId = EVRButtonId.k_EButton_SteamVR_Touchpad)
  91. {
  92. var system = OpenVR.System;
  93. if (system != null)
  94. {
  95. var axisId = (uint)buttonId - (uint)EVRButtonId.k_EButton_Axis0;
  96. system.TriggerHapticPulse(index, axisId, (char)durationMicroSec);
  97. }
  98. }
  99. public float hairTriggerDelta = 0.1f; // amount trigger must be pulled or released to change state
  100. float hairTriggerLimit;
  101. bool hairTriggerState, hairTriggerPrevState;
  102. void UpdateHairTrigger()
  103. {
  104. hairTriggerPrevState = hairTriggerState;
  105. var value = state.rAxis1.x; // trigger
  106. if (hairTriggerState)
  107. {
  108. if (value < hairTriggerLimit - hairTriggerDelta || value <= 0.0f)
  109. hairTriggerState = false;
  110. }
  111. else
  112. {
  113. if (value > hairTriggerLimit + hairTriggerDelta || value >= 1.0f)
  114. hairTriggerState = true;
  115. }
  116. hairTriggerLimit = hairTriggerState ? Mathf.Max(hairTriggerLimit, value) : Mathf.Min(hairTriggerLimit, value);
  117. }
  118. public bool GetHairTrigger() { Update(); return hairTriggerState; }
  119. public bool GetHairTriggerDown() { Update(); return hairTriggerState && !hairTriggerPrevState; }
  120. public bool GetHairTriggerUp() { Update(); return !hairTriggerState && hairTriggerPrevState; }
  121. }
  122. private static Device[] devices;
  123. public static Device Input(int deviceIndex)
  124. {
  125. if (devices == null)
  126. {
  127. devices = new Device[OpenVR.k_unMaxTrackedDeviceCount];
  128. for (uint i = 0; i < devices.Length; i++)
  129. devices[i] = new Device(i);
  130. }
  131. return devices[deviceIndex];
  132. }
  133. public static void Update()
  134. {
  135. for (int i = 0; i < OpenVR.k_unMaxTrackedDeviceCount; i++)
  136. Input(i).Update();
  137. }
  138. // This helper can be used in a variety of ways. Beware that indices may change
  139. // as new devices are dynamically added or removed, controllers are physically
  140. // swapped between hands, arms crossed, etc.
  141. public enum DeviceRelation
  142. {
  143. First,
  144. // radially
  145. Leftmost,
  146. Rightmost,
  147. // distance - also see vr.hmd.GetSortedTrackedDeviceIndicesOfClass
  148. FarthestLeft,
  149. FarthestRight,
  150. }
  151. public static int GetDeviceIndex(DeviceRelation relation,
  152. ETrackedDeviceClass deviceClass = ETrackedDeviceClass.Controller,
  153. int relativeTo = (int)OpenVR.k_unTrackedDeviceIndex_Hmd) // use -1 for absolute tracking space
  154. {
  155. var result = -1;
  156. var invXform = ((uint)relativeTo < OpenVR.k_unMaxTrackedDeviceCount) ?
  157. Input(relativeTo).transform.GetInverse() : SteamVR_Utils.RigidTransform.identity;
  158. var system = OpenVR.System;
  159. if (system == null)
  160. return result;
  161. var best = -float.MaxValue;
  162. for (int i = 0; i < OpenVR.k_unMaxTrackedDeviceCount; i++)
  163. {
  164. if (i == relativeTo || system.GetTrackedDeviceClass((uint)i) != deviceClass)
  165. continue;
  166. var device = Input(i);
  167. if (!device.connected)
  168. continue;
  169. if (relation == DeviceRelation.First)
  170. return i;
  171. float score;
  172. var pos = invXform * device.transform.pos;
  173. if (relation == DeviceRelation.FarthestRight)
  174. {
  175. score = pos.x;
  176. }
  177. else if (relation == DeviceRelation.FarthestLeft)
  178. {
  179. score = -pos.x;
  180. }
  181. else
  182. {
  183. var dir = new Vector3(pos.x, 0.0f, pos.z).normalized;
  184. var dot = Vector3.Dot(dir, Vector3.forward);
  185. var cross = Vector3.Cross(dir, Vector3.forward);
  186. if (relation == DeviceRelation.Leftmost)
  187. {
  188. score = (cross.y > 0.0f) ? 2.0f - dot : dot;
  189. }
  190. else
  191. {
  192. score = (cross.y < 0.0f) ? 2.0f - dot : dot;
  193. }
  194. }
  195. if (score > best)
  196. {
  197. result = i;
  198. best = score;
  199. }
  200. }
  201. return result;
  202. }
  203. }