2using Newtonsoft.Json.Linq;
4using System.Collections.Generic;
7using UnityEngine.Events;
8using UnityEngine.LowLevel;
9using UnityEngine.PlayerLoop;
28 public ETrackingUniverseOrigin
trackingUniverse = ETrackingUniverseOrigin.TrackingUniverseStanding;
36 public Dictionary<string, string>
Bindings {
get;
set; } =
new Dictionary<string, string>();
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;
49 Dictionary<string, string> trackerBindings =
new Dictionary<string, string>();
51 string steamVrSettingsPath;
52 if (_steamVrConfigPath is
null)
54 steamVrSettingsPath = Path.Combine(OpenVR.RuntimePath(),
"../../../config/steamvr.vrsettings");
58 steamVrSettingsPath = Path.Combine(_steamVrConfigPath,
"steamvr.vrsettings");
61 if (!File.Exists(steamVrSettingsPath))
63 Debug.LogWarning(
"[ViveTrackers] Could not find SteamVR configuration file!");
64 return trackerBindings;
67 var json = File.ReadAllText(steamVrSettingsPath);
68 var steamVrSettings = JObject.Parse(json);
70 if (steamVrSettings.ContainsKey(
"trackers"))
72 var trackers = steamVrSettings[
"trackers"].ToObject<Dictionary<string, string>>();
73 foreach (var pair
in trackers)
75 trackerBindings.Add(pair.Key.Replace(
"/devices/htc/vive_tracker",
""), pair.Value);
79 return trackerBindings;
86 var newBindings = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
100 json = JsonConvert.SerializeObject(
Bindings);
103 catch (JsonException)
112 if (OpenVR.System ==
null)
114 return "<vr system stopped>";
116 var error = ETrackedPropertyError.TrackedProp_Success;
117 var capacity = OpenVR.System.GetStringTrackedDeviceProperty(deviceId, prop,
null, 0, ref error);
120 var result =
new System.Text.StringBuilder((
int)capacity);
121 OpenVR.System.GetStringTrackedDeviceProperty(deviceId, prop, result, capacity, ref error);
122 return result.ToString();
124 return (error != ETrackedPropertyError.TrackedProp_Success) ? error.ToString() :
"<unknown>";
130 _onDeviceConnected += OnDeviceConnected;
137 private void OnEnable()
139 Application.onBeforeRender += OnBeforeRender;
140 OVRT_Events.TrackedDeviceConnected.AddListener(_onDeviceConnected);
143 private void OnDisable()
145 Application.onBeforeRender -= OnBeforeRender;
146 OVRT_Events.TrackedDeviceConnected.RemoveListener(_onDeviceConnected);
152 if (!OpenVR.IsRuntimeInstalled())
159 if (!OpenVR.IsHmdPresent())
161 var dummyError = EVRInitError.None;
162 OpenVR.Init(ref dummyError, EVRApplicationType.VRApplication_Scene);
163 System.Threading.SpinWait.SpinUntil(() => OpenVR.IsHmdPresent(), TimeSpan.FromSeconds(10));
167 var initError = EVRInitError.None;
168 OpenVR.Init(ref initError, EVRApplicationType.VRApplication_Other);
170 if (initError != EVRInitError.None)
172 var initErrorString = OpenVR.GetStringForHmdError(initError);
173 Debug.LogError($
"[ViveTrackers] Could not initialize OpenVR tracking: {initErrorString}");
177 _isInitialized =
true;
179 var openVrPathsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"openvr",
"openvrpaths.vrpath");
180 if (File.Exists(openVrPathsConfigPath))
182 var json = File.ReadAllText(openVrPathsConfigPath);
183 var openVrPathsConfig = JObject.Parse(json);
185 if (openVrPathsConfig.ContainsKey(
"config"))
187 var paths = openVrPathsConfig[
"config"].ToObject<List<string>>();
190 _steamVrConfigPath = paths[0];
196 Debug.LogWarning($
"[ViveTrackers] Could not find {openVrPathsConfigPath}!");
199 Debug.Log($
"[ViveTrackers] Initialized OpenVR tracking.");
201 UpdateSteamVrTrackerBindings();
212 void OnApplicationQuit()
217 private void OnBeforeRender()
226 float secondsSinceLastVsync = Time.realtimeSinceStartup - lastVsyncTimestamp;
229 UpdatePoses(secondsFromNow);
233 private void OnDeviceConnected(
int index,
bool connected)
238 private void Update()
240 if (!_isInitialized)
return;
243 if (OpenVR.System ==
null)
249 var vrEvent =
new VREvent_t();
250 while (OpenVR.System.PollNextEvent(ref vrEvent, (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VREvent_t))))
252 switch ((EVREventType)vrEvent.eventType)
254 case EVREventType.VREvent_TrackedDeviceActivated:
255 OVRT_Events.TrackedDeviceConnected.Invoke((
int)vrEvent.trackedDeviceIndex,
true);
257 case EVREventType.VREvent_TrackedDeviceDeactivated:
258 OVRT_Events.TrackedDeviceConnected.Invoke((
int)vrEvent.trackedDeviceIndex,
false);
260 case EVREventType.VREvent_TrackedDeviceRoleChanged:
261 OVRT_Events.TrackedDeviceRoleChanged.Invoke((
int)vrEvent.trackedDeviceIndex);
263 case EVREventType.VREvent_ButtonPress:
264 OVRT_Events.ButtonPressed.Invoke((
int)vrEvent.trackedDeviceIndex, (EVRButtonId)vrEvent.data.controller.button,
true);
266 case EVREventType.VREvent_ButtonUnpress:
267 OVRT_Events.ButtonPressed.Invoke((
int)vrEvent.trackedDeviceIndex, (EVRButtonId)vrEvent.data.controller.button,
false);
269 case EVREventType.VREvent_TrackersSectionSettingChanged:
271 Invoke(nameof(UpdateSteamVrTrackerBindings), 1.0f);
273 case EVREventType.VREvent_ShowRenderModels:
274 OVRT_Events.HideRenderModelsChanged.Invoke(
false);
276 case EVREventType.VREvent_HideRenderModels:
277 OVRT_Events.HideRenderModelsChanged.Invoke(
true);
279 case EVREventType.VREvent_ModelSkinSettingsHaveChanged:
280 OVRT_Events.ModelSkinSettingsHaveChanged.Invoke();
282 case EVREventType.VREvent_Quit:
283 case EVREventType.VREvent_ProcessQuit:
284 case EVREventType.VREvent_DriverRequestedQuit:
294 private void UpdatePoses()
296 var compositor = OpenVR.Compositor;
297 if (compositor !=
null)
299 compositor.GetLastPoses(_poses, gamePoses);
300 OVRT_Events.NewPoses.Invoke(_poses);
304 private void UpdatePoses(
float predictedSecondsToPhotonsFromNow)
306 if (!_isInitialized)
return;
309 if (OpenVR.System ==
null)
315 OVRT_Events.NewPoses.Invoke(_poses);
317 for (uint i = 0; i < _poses.Length; i++)
319 var pose = _poses[i];
320 if (pose.bDeviceIsConnected && pose.bPoseIsValid)
322 var serialNumber =
GetStringProperty(ETrackedDeviceProperty.Prop_SerialNumber_String, i);
325 if (
Bindings.TryGetValue(serialNumber, out binding))
327 OVRT_Events.NewBoundPose.Invoke(binding, pose, (
int)i);
332 string trackerBinding;
335 OVRT_Events.NewBoundPose.Invoke(trackerBinding, pose, (
int)i);
342 private void UpdateSteamVrTrackerBindings()
346 OVRT_Events.TrackerRolesChanged.Invoke();
349 private static bool runningTemporarySession =
false;
352 if (Application.isEditor)
356 EVRInitError initError = EVRInitError.None;
357 OpenVR.GetGenericInterface(OpenVR.IVRCompositor_Version, ref initError);
358 bool needsInit = initError != EVRInitError.None;
362 EVRInitError error = EVRInitError.None;
363 OpenVR.Init(ref error, EVRApplicationType.VRApplication_Other);
365 if (error != EVRInitError.None)
367 Debug.LogError(
"[ViveTrackers] Could not initialize OpenVR tracking: " + error.ToString());
371 runningTemporarySession =
true;
383 if (runningTemporarySession)
386 runningTemporarySession =
false;
390 [RuntimeInitializeOnLoadMethod]
391 private static void AddPostVsyncCallback()
393 var defaultSystems = PlayerLoop.GetDefaultPlayerLoop();
395 var updateVsyncTimestampSystem =
new PlayerLoopSystem
397 subSystemList =
null,
403 PlayerLoopSystem newPlayerLoop =
new()
405 loopConditionFunction = defaultSystems.loopConditionFunction,
406 type = defaultSystems.type,
407 updateDelegate = defaultSystems.updateDelegate,
408 updateFunction = defaultSystems.updateFunction
411 List<PlayerLoopSystem> newSubSystemList =
new();
413 foreach (var subSystem
in defaultSystems.subSystemList)
415 newSubSystemList.Add(subSystem);
417 if (subSystem.type == typeof(TimeUpdate))
418 newSubSystemList.Add(updateVsyncTimestampSystem);
421 newPlayerLoop.subSystemList = newSubSystemList.ToArray();
423 PlayerLoop.SetPlayerLoop(newPlayerLoop);
426 private static void UpdateVsyncTimestamp()
428 lastVsyncTimestamp = Time.realtimeSinceStartup;
431 private static float lastVsyncTimestamp = 0;
Manages connection to OpenVR and dispatches new poses and events. You should only have one of these i...
static bool InitializeTemporarySession()
Dictionary< string, string > Bindings
Dictionary< string, string > SteamVrTrackerBindings
string GetStringProperty(ETrackedDeviceProperty prop, uint deviceId)
bool doUpdatePosesBeforeRendering
bool useSteamVrTrackerRoles
bool ConvertBindingsToJson(out string json)
float vsyncToPhotonsSeconds
Dictionary< string, string > GetSteamVrTrackerBindings()
bool[] ConnectedDeviceIndices
static void ExitTemporarySession()
bool ReadBindingsFromJson(string json)
ETrackingUniverseOrigin trackingUniverse