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