Spelunx Cavern SDK
 
Loading...
Searching...
No Matches
Vive_Manager.cs
Go to the documentation of this file.
1using Newtonsoft.Json;
2using Newtonsoft.Json.Linq;
3using System;
4using System.Collections.Generic;
5using System.IO;
6using UnityEngine;
7using UnityEngine.Events;
8using UnityEngine.LowLevel;
9using UnityEngine.PlayerLoop;
10using Valve.VR;
11
12namespace Spelunx.Vive
13{
14 /// <summary>
15 /// Manages connection to OpenVR and dispatches new poses and events.
16 /// You should only have one of these in a scene.
17 /// </summary>
18 public class Vive_Manager : MonoBehaviour
19 {
20 public enum UpdateMode
21 {
23 Update,
26 }
27
28 public ETrackingUniverseOrigin trackingUniverse = ETrackingUniverseOrigin.TrackingUniverseStanding;
29 public float displayFrequency = 0f;
30 public bool usePosePrediction = true;
31 public bool doUpdatePosesBeforeRendering = true;
32 public float vsyncToPhotonsSeconds = 0.03f;
33 public bool useSteamVrTrackerRoles = true;
34
35 public bool[] ConnectedDeviceIndices { get; private set; } = new bool[OpenVR.k_unMaxTrackedDeviceCount];
36 public Dictionary<string, string> Bindings { get; set; } = new Dictionary<string, string>();
37 public Dictionary<string, string> SteamVrTrackerBindings { get; private set; } = new Dictionary<string, string>();
38
39 // This keeps track of if we've already done the setup so we don't redo it
40 // and prevents crashes when changing scenes
41 private static bool _isInitialized = false;
42 private TrackedDevicePose_t[] _poses = new TrackedDevicePose_t[OpenVR.k_unMaxTrackedDeviceCount];
43 private TrackedDevicePose_t[] gamePoses = new TrackedDevicePose_t[0];
44 private UnityAction<int, bool> _onDeviceConnected;
45 private string _steamVrConfigPath = null;
46
47 public Dictionary<string, string> GetSteamVrTrackerBindings()
48 {
49 Dictionary<string, string> trackerBindings = new Dictionary<string, string>();
50
51 string steamVrSettingsPath;
52 if (_steamVrConfigPath is null)
53 {
54 steamVrSettingsPath = Path.Combine(OpenVR.RuntimePath(), "../../../config/steamvr.vrsettings");
55 }
56 else
57 {
58 steamVrSettingsPath = Path.Combine(_steamVrConfigPath, "steamvr.vrsettings");
59 }
60
61 if (!File.Exists(steamVrSettingsPath))
62 {
63 Debug.LogWarning("[ViveTrackers] Could not find SteamVR configuration file!");
64 return trackerBindings;
65 }
66
67 var json = File.ReadAllText(steamVrSettingsPath);
68 var steamVrSettings = JObject.Parse(json);
69
70 if (steamVrSettings.ContainsKey("trackers"))
71 {
72 var trackers = steamVrSettings["trackers"].ToObject<Dictionary<string, string>>();
73 foreach (var pair in trackers)
74 {
75 trackerBindings.Add(pair.Key.Replace("/devices/htc/vive_tracker", ""), pair.Value);
76 }
77 }
78
79 return trackerBindings;
80 }
81
82 public bool ReadBindingsFromJson(string json)
83 {
84 try
85 {
86 var newBindings = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
87 Bindings = newBindings;
88 return true;
89 }
90 catch (JsonException)
91 {
92 return false;
93 }
94 }
95
96 public bool ConvertBindingsToJson(out string json)
97 {
98 try
99 {
100 json = JsonConvert.SerializeObject(Bindings);
101 return true;
102 }
103 catch (JsonException)
104 {
105 json = "";
106 return false;
107 }
108 }
109
110 public string GetStringProperty(ETrackedDeviceProperty prop, uint deviceId)
111 {
112 if (OpenVR.System == null)
113 {
114 return "<vr system stopped>";
115 }
116 var error = ETrackedPropertyError.TrackedProp_Success;
117 var capacity = OpenVR.System.GetStringTrackedDeviceProperty(deviceId, prop, null, 0, ref error);
118 if (capacity > 1)
119 {
120 var result = new System.Text.StringBuilder((int)capacity);
121 OpenVR.System.GetStringTrackedDeviceProperty(deviceId, prop, result, capacity, ref error);
122 return result.ToString();
123 }
124 return (error != ETrackedPropertyError.TrackedProp_Success) ? error.ToString() : "<unknown>";
125 }
126
127
128 private void Awake()
129 {
130 _onDeviceConnected += OnDeviceConnected;
131 if (!_isInitialized)
132 {
133 Init();
134 }
135 }
136
137 private void OnEnable()
138 {
139 Application.onBeforeRender += OnBeforeRender;
140 OVRT_Events.TrackedDeviceConnected.AddListener(_onDeviceConnected);
141 }
142
143 private void OnDisable()
144 {
145 Application.onBeforeRender -= OnBeforeRender;
146 OVRT_Events.TrackedDeviceConnected.RemoveListener(_onDeviceConnected);
147 Array.Clear(ConnectedDeviceIndices, 0, ConnectedDeviceIndices.Length);
148 }
149
150 private void Init()
151 {
152 if (!OpenVR.IsRuntimeInstalled())
153 {
154 // Debug.LogError("[OVRT] SteamVR runtime not installed!");
155 return;
156 }
157
158 // Ensure SteamVR is running
159 if (!OpenVR.IsHmdPresent())
160 {
161 var dummyError = EVRInitError.None;
162 OpenVR.Init(ref dummyError, EVRApplicationType.VRApplication_Scene);
163 System.Threading.SpinWait.SpinUntil(() => OpenVR.IsHmdPresent(), TimeSpan.FromSeconds(10));
164 OpenVR.Shutdown();
165 }
166
167 var initError = EVRInitError.None;
168 OpenVR.Init(ref initError, EVRApplicationType.VRApplication_Other);
169
170 if (initError != EVRInitError.None)
171 {
172 var initErrorString = OpenVR.GetStringForHmdError(initError);
173 Debug.LogError($"[ViveTrackers] Could not initialize OpenVR tracking: {initErrorString}");
174 return;
175 }
176
177 _isInitialized = true;
178
179 var openVrPathsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "openvr", "openvrpaths.vrpath");
180 if (File.Exists(openVrPathsConfigPath))
181 {
182 var json = File.ReadAllText(openVrPathsConfigPath);
183 var openVrPathsConfig = JObject.Parse(json);
184
185 if (openVrPathsConfig.ContainsKey("config"))
186 {
187 var paths = openVrPathsConfig["config"].ToObject<List<string>>();
188 if (paths.Count > 0)
189 {
190 _steamVrConfigPath = paths[0];
191 }
192 }
193 }
194 else
195 {
196 Debug.LogWarning($"[ViveTrackers] Could not find {openVrPathsConfigPath}!");
197 }
198
199 Debug.Log($"[ViveTrackers] Initialized OpenVR tracking.");
200
201 UpdateSteamVrTrackerBindings();
202 }
203
204 private void Start()
205 {
206 // if (doUpdatePosesBeforeRendering && QualitySettings.vSyncCount == 0)
207 // {
208 // Debug.LogWarning("Pose prediction requires vertical synchronization for sensible prediction results. Set QualitySettings.vSyncCount to 1 or higher.");
209 // }
210 }
211
212 void OnApplicationQuit()
213 {
214 OpenVR.Shutdown();
215 }
216
217 private void OnBeforeRender()
218 {
220 {
221 if (displayFrequency <= 0)
222 {
223 displayFrequency = (float)Screen.currentResolution.refreshRateRatio.value;
224 }
225
226 float secondsSinceLastVsync = Time.realtimeSinceStartup - lastVsyncTimestamp;
227 float frameDuration = 1f / Math.Max(displayFrequency, 1);
228 float secondsFromNow = Mathf.Max(0, frameDuration - secondsSinceLastVsync) + vsyncToPhotonsSeconds;
229 UpdatePoses(secondsFromNow);
230 }
231 }
232
233 private void OnDeviceConnected(int index, bool connected)
234 {
235 ConnectedDeviceIndices[index] = connected;
236 }
237
238 private void Update()
239 {
240 if (!_isInitialized) return;
241
242 //without this, unity will crash
243 if (OpenVR.System == null)
244 {
245 return;
246 }
247
248 // Process OpenVR event queue
249 var vrEvent = new VREvent_t();
250 while (OpenVR.System.PollNextEvent(ref vrEvent, (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VREvent_t))))
251 {
252 switch ((EVREventType)vrEvent.eventType)
253 {
254 case EVREventType.VREvent_TrackedDeviceActivated:
255 OVRT_Events.TrackedDeviceConnected.Invoke((int)vrEvent.trackedDeviceIndex, true);
256 break;
257 case EVREventType.VREvent_TrackedDeviceDeactivated:
258 OVRT_Events.TrackedDeviceConnected.Invoke((int)vrEvent.trackedDeviceIndex, false);
259 break;
260 case EVREventType.VREvent_TrackedDeviceRoleChanged:
261 OVRT_Events.TrackedDeviceRoleChanged.Invoke((int)vrEvent.trackedDeviceIndex);
262 break;
263 case EVREventType.VREvent_ButtonPress:
264 OVRT_Events.ButtonPressed.Invoke((int)vrEvent.trackedDeviceIndex, (EVRButtonId)vrEvent.data.controller.button, true);
265 break;
266 case EVREventType.VREvent_ButtonUnpress:
267 OVRT_Events.ButtonPressed.Invoke((int)vrEvent.trackedDeviceIndex, (EVRButtonId)vrEvent.data.controller.button, false);
268 break;
269 case EVREventType.VREvent_TrackersSectionSettingChanged:
270 // Allow some time until SteamVR configuration file has been updated on disk
271 Invoke(nameof(UpdateSteamVrTrackerBindings), 1.0f);
272 break;
273 case EVREventType.VREvent_ShowRenderModels:
274 OVRT_Events.HideRenderModelsChanged.Invoke(false);
275 break;
276 case EVREventType.VREvent_HideRenderModels:
277 OVRT_Events.HideRenderModelsChanged.Invoke(true);
278 break;
279 case EVREventType.VREvent_ModelSkinSettingsHaveChanged:
280 OVRT_Events.ModelSkinSettingsHaveChanged.Invoke();
281 break;
282 case EVREventType.VREvent_Quit:
283 case EVREventType.VREvent_ProcessQuit:
284 case EVREventType.VREvent_DriverRequestedQuit:
285 return;
286 default:
287 break;
288 }
289 }
290
291 UpdatePoses(0);
292 }
293
294 private void UpdatePoses()
295 {
296 var compositor = OpenVR.Compositor;
297 if (compositor != null)
298 {
299 compositor.GetLastPoses(_poses, gamePoses);
300 OVRT_Events.NewPoses.Invoke(_poses);
301 // OVRT_Events.NewPosesApplied.Send();
302 }
303 }
304 private void UpdatePoses(float predictedSecondsToPhotonsFromNow)
305 {
306 if (!_isInitialized) return;
307
308 //without this, unity will crash
309 if (OpenVR.System == null)
310 {
311 return;
312 }
313
314 OpenVR.System.GetDeviceToAbsoluteTrackingPose(trackingUniverse, usePosePrediction ? predictedSecondsToPhotonsFromNow : 0f, _poses);
315 OVRT_Events.NewPoses.Invoke(_poses);
316
317 for (uint i = 0; i < _poses.Length; i++)
318 {
319 var pose = _poses[i];
320 if (pose.bDeviceIsConnected && pose.bPoseIsValid)
321 {
322 var serialNumber = GetStringProperty(ETrackedDeviceProperty.Prop_SerialNumber_String, i);
323
324 string binding;
325 if (Bindings.TryGetValue(serialNumber, out binding))
326 {
327 OVRT_Events.NewBoundPose.Invoke(binding, pose, (int)i);
328 }
329
331 {
332 string trackerBinding;
333 if (SteamVrTrackerBindings.TryGetValue(serialNumber, out trackerBinding))
334 {
335 OVRT_Events.NewBoundPose.Invoke(trackerBinding, pose, (int)i);
336 }
337 }
338 }
339 }
340 }
341
342 private void UpdateSteamVrTrackerBindings()
343 {
344 var trackerBindings = GetSteamVrTrackerBindings();
345 SteamVrTrackerBindings = trackerBindings;
346 OVRT_Events.TrackerRolesChanged.Invoke();
347 }
348
349 private static bool runningTemporarySession = false;
350 public static bool InitializeTemporarySession()
351 {
352 if (Application.isEditor)
353 {
354 //bool needsInit = (!active && !usingNativeSupport && !runningTemporarySession);
355
356 EVRInitError initError = EVRInitError.None;
357 OpenVR.GetGenericInterface(OpenVR.IVRCompositor_Version, ref initError);
358 bool needsInit = initError != EVRInitError.None;
359
360 if (needsInit)
361 {
362 EVRInitError error = EVRInitError.None;
363 OpenVR.Init(ref error, EVRApplicationType.VRApplication_Other);
364
365 if (error != EVRInitError.None)
366 {
367 Debug.LogError("[ViveTrackers] Could not initialize OpenVR tracking: " + error.ToString());
368 return false;
369 }
370
371 runningTemporarySession = true;
372 }
373
374
375 return needsInit;
376 }
377
378 return false;
379 }
380
381 public static void ExitTemporarySession()
382 {
383 if (runningTemporarySession)
384 {
385 OpenVR.Shutdown();
386 runningTemporarySession = false;
387 }
388 }
389
390 [RuntimeInitializeOnLoadMethod]
391 private static void AddPostVsyncCallback()
392 {
393 var defaultSystems = PlayerLoop.GetDefaultPlayerLoop();
394
395 var updateVsyncTimestampSystem = new PlayerLoopSystem
396 {
397 subSystemList = null,
398 updateDelegate = UpdateVsyncTimestamp,
399 type = typeof(UpdateVsyncTimestamp)
400 };
401
402
403 PlayerLoopSystem newPlayerLoop = new()
404 {
405 loopConditionFunction = defaultSystems.loopConditionFunction,
406 type = defaultSystems.type,
407 updateDelegate = defaultSystems.updateDelegate,
408 updateFunction = defaultSystems.updateFunction
409 };
410
411 List<PlayerLoopSystem> newSubSystemList = new();
412
413 foreach (var subSystem in defaultSystems.subSystemList)
414 {
415 newSubSystemList.Add(subSystem);
416
417 if (subSystem.type == typeof(TimeUpdate))
418 newSubSystemList.Add(updateVsyncTimestampSystem);
419 }
420
421 newPlayerLoop.subSystemList = newSubSystemList.ToArray();
422
423 PlayerLoop.SetPlayerLoop(newPlayerLoop);
424 }
425
426 private static void UpdateVsyncTimestamp()
427 {
428 lastVsyncTimestamp = Time.realtimeSinceStartup;
429 }
430
431 private static float lastVsyncTimestamp = 0;
432 }
433
434 public class UpdateVsyncTimestamp { }
435}
Manages connection to OpenVR and dispatches new poses and events. You should only have one of these i...
Definition: Vive_Manager.cs:19
static bool InitializeTemporarySession()
Dictionary< string, string > Bindings
Definition: Vive_Manager.cs:36
Dictionary< string, string > SteamVrTrackerBindings
Definition: Vive_Manager.cs:37
string GetStringProperty(ETrackedDeviceProperty prop, uint deviceId)
bool ConvertBindingsToJson(out string json)
Definition: Vive_Manager.cs:96
Dictionary< string, string > GetSteamVrTrackerBindings()
Definition: Vive_Manager.cs:47
static void ExitTemporarySession()
bool ReadBindingsFromJson(string json)
Definition: Vive_Manager.cs:82
ETrackingUniverseOrigin trackingUniverse
Definition: Vive_Manager.cs:28