diff --git a/driver/slimevr/resources/icons/slimevr_tracker_icon.png b/driver/slimevr/resources/icons/slimevr_tracker_icon.png
new file mode 100644
index 00000000..e3e50fbc
Binary files /dev/null and b/driver/slimevr/resources/icons/slimevr_tracker_icon.png differ
diff --git a/driver/slimevr/resources/input/actions.json b/driver/slimevr/resources/input/actions.json
new file mode 100644
index 00000000..6c62a384
--- /dev/null
+++ b/driver/slimevr/resources/input/actions.json
@@ -0,0 +1,41 @@
+{
+ "action_manifest_version": 1,
+ "controller_type": "slimevr_virtual_controller",
+ "interaction_profile": "/interaction_profiles/valve/index_controller",
+ "description": "SlimeVR virtual controller",
+ "app_key": "openvr.component.slimevr",
+ "actions": {
+ "pose": [
+ { "name": "/actions/main/in/PoseRaw" },
+ { "name": "/actions/main/in/PoseAim" }
+ ],
+ "haptic": [
+ { "name": "/actions/main/out/Haptic" }
+ ],
+ "boolean": [
+ { "name": "/actions/main/in/DoubleTap" },
+ { "name": "/actions/main/in/TripleTap" },
+ { "name": "/actions/main/in/A" },
+ { "name": "/actions/main/in/B" },
+ { "name": "/actions/main/in/System" },
+ { "name": "/actions/main/in/TriggerClick" },
+ { "name": "/actions/main/in/GripTouch" },
+ { "name": "/actions/main/in/TrackpadClick" },
+ { "name": "/actions/main/in/TrackpadTouch" },
+ { "name": "/actions/main/in/ThumbstickClick" },
+ { "name": "/actions/main/in/ThumbstickTouch" }
+ ],
+ "scalar": [
+ { "name": "/actions/main/in/TriggerValue" },
+ { "name": "/actions/main/in/GripValue" },
+ { "name": "/actions/main/in/TrackpadX" },
+ { "name": "/actions/main/in/TrackpadY" },
+ { "name": "/actions/main/in/ThumbstickX" },
+ { "name": "/actions/main/in/ThumbstickY" }
+ ],
+ "skeleton": [
+ { "name": "/actions/main/in/SkeletonLeft" },
+ { "name": "/actions/main/in/SkeletonRight" }
+ ]
+ }
+}
diff --git a/driver/slimevr/resources/input/example_controller_bindings.json b/driver/slimevr/resources/input/example_controller_bindings.json
deleted file mode 100644
index 51099b15..00000000
--- a/driver/slimevr/resources/input/example_controller_bindings.json
+++ /dev/null
@@ -1,83 +0,0 @@
-{
- "jsonid": "input_profile",
- "controller_type": "example_controller",
- "device_class": "TrackedDeviceClass_Controller",
- "resource_root": "example",
- "driver_name": "example",
- "input_bindingui_mode": "controller_handed",
- "should_show_binding_errors": true,
- "input_bindingui_left": {
- "image": "{example}/icons/example_controller_left.svg"
- },
- "input_bindingui_right": {
- "image": "{example}/icons/example_controller_right.svg"
- },
- "input_source": {
- "/pose/raw" : {
- "type" : "pose",
- "binding_image_point" : [ 0,0 ]
- },
- "/output/haptic": {
- "type": "vibration",
- "binding_image_point": [0,0]
- },
- "/input/a": {
- "type": "button",
- "click": true,
- "touch" : true,
- "binding_image_point": [0,0]
- },
- "/input/b": {
- "type": "button",
- "click": true,
- "touch" : true,
- "binding_image_point": [0,0]
- },
- "/input/trigger" : {
- "type" : "trigger",
- "click" : true,
- "touch" : true,
- "value" : true,
- "binding_image_point" : [ 0, 0 ]
- },
- "/input/grip" : {
- "type" : "trigger",
- "force" : true,
- "value" : true,
- "touch" : true,
- "input_activity_path" : "/input/grip/force",
- "input_activity_threshold" : 0.1,
- "binding_image_point" : [ 0, 0 ]
- },
- "/input/system": {
- "type": "button",
- "click": true,
- "touch" : true,
- "binding_image_point": [0,0]
- },
- "/input/trackpad": {
- "type": "trackpad",
- "click": true,
- "touch" : true,
- "binding_image_point": [0,0]
- },
- "/input/joystick": {
- "type": "joystick",
- "click": true,
- "touch": true,
- "binding_image_point": [0,0]
- },
- "/input/skeleton/left" : {
- "type" : "skeleton",
- "skeleton": "/skeleton/hand/left",
- "side" : "left",
- "binding_image_point" : [ 0, 0 ]
- },
- "/input/skeleton/right" : {
- "type" : "skeleton",
- "skeleton": "/skeleton/hand/right",
- "side" : "right",
- "binding_image_point" : [ 0, 0 ]
- }
- }
-}
diff --git a/driver/slimevr/resources/input/example_controller_left.svg b/driver/slimevr/resources/input/example_controller_left.svg
deleted file mode 100644
index b6e47d8a..00000000
--- a/driver/slimevr/resources/input/example_controller_left.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/driver/slimevr/resources/input/example_controller_right.svg b/driver/slimevr/resources/input/example_controller_right.svg
deleted file mode 100644
index 95b609dc..00000000
--- a/driver/slimevr/resources/input/example_controller_right.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/driver/slimevr/resources/input/example_tracker_bindings.json b/driver/slimevr/resources/input/example_tracker_bindings.json
deleted file mode 100644
index 763d09e9..00000000
--- a/driver/slimevr/resources/input/example_tracker_bindings.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "jsonid": "input_profile",
- "controller_type": "example_tracker",
- "device_class": "TrackedDeviceClass_GenericTracker",
- "resource_root": "example",
- "driver_name": "example",
- "input_bindingui_mode": "single_device",
- "should_show_binding_errors": true,
- "input_bindingui_left": {
- "transform": "scale(-1,1)",
- "image": "{example}/icons/example_tracker.svg"
- },
- "input_bindingui_right": {
- "image": "{example}/icons/example_tracker.svg"
- },
- "input_source": {
- "/pose/raw" : {
- "type" : "pose",
- "binding_image_point" : [ 0,0 ]
- },
- "/output/haptic": {
- "type": "vibration",
- "binding_image_point": [0,0]
- },
- "/input/system": {
- "type": "button",
- "click": true,
- "binding_image_point": [0,0]
- }
- }
-}
diff --git a/driver/slimevr/resources/input/slimevr_controller_bindings.json b/driver/slimevr/resources/input/slimevr_controller_bindings.json
new file mode 100644
index 00000000..aa9f7555
--- /dev/null
+++ b/driver/slimevr/resources/input/slimevr_controller_bindings.json
@@ -0,0 +1,109 @@
+{
+ "jsonid": "input_profile",
+ "controller_type": "slimevr_virtual_controller",
+ "device_class": "TrackedDeviceClass_Controller",
+ "resource_root": "slimevr",
+ "driver_name": "slimevr",
+ "input_bindingui_mode": "controller_handed",
+ "should_show_binding_errors": true,
+ "compatibility_mode_controller_type": "knuckles",
+ "input_bindingui_left": {
+ "image": "{slimevr}/icons/slimevr_tracker_icon.png"
+ },
+ "input_bindingui_right": {
+ "image": "{slimevr}/icons/slimevr_tracker_icon.png"
+ },
+ "input_source": {
+ "/input/system": {
+ "type": "button",
+ "binding_image_point": [ 34, 45 ],
+ "order": 1
+ },
+ "/input/a": {
+ "type": "button",
+ "binding_image_point": [ 26, 42 ],
+ "order": 5
+ },
+ "/input/b": {
+ "type": "button",
+ "binding_image_point": [ 18, 37 ],
+ "order": 6
+ },
+ "/input/trigger": {
+ "type": "trigger",
+ "binding_image_point": [ 11, 60 ],
+ "order": 2
+ },
+ "/input/trackpad": {
+ "type": "trackpad",
+ "binding_image_point": [ 27, 37 ],
+ "order": 3
+ },
+ "/input/grip": {
+ "type": "trigger",
+ "binding_image_point": [ 47, 86 ],
+ "order": 7
+ },
+ "/input/thumbstick": {
+ "type": "joystick",
+ "binding_image_point": [ 31, 26 ],
+ "order": 4
+ },
+ "/input/pinch": {
+ "type": "pinch",
+ "binding_image_point": [ 27, 37 ],
+ "value_source": "/input/trigger",
+ "capsense_source": "/input/finger/index",
+ "force_source": "/input/trackpad",
+ "order": 7
+ },
+
+ "/input/finger/index": {
+ "type": "trigger",
+ "visibility": "InputValueVisibility_AvailableButHidden",
+ "binding_image_point": [ 56, 86 ],
+ "order": 7
+ },
+ "/input/finger/middle": {
+ "type": "trigger",
+ "visibility": "InputValueVisibility_AvailableButHidden",
+ "binding_image_point": [ 56, 86 ],
+ "order": 8
+ },
+ "/input/finger/ring": {
+ "type": "trigger",
+ "visibility": "InputValueVisibility_AvailableButHidden",
+ "binding_image_point": [ 56, 86 ],
+ "order": 9
+ },
+ "/input/finger/pinky": {
+ "type": "trigger",
+ "visibility": "InputValueVisibility_AvailableButHidden",
+ "binding_image_point": [ 56, 86 ],
+ "order": 10
+ },
+
+ "/pose/raw": {
+ "type": "pose",
+ "binding_image_point": [ 5, 35 ]
+ },
+ "/input/skeleton/left": {
+ "type": "skeleton",
+ "skeleton": "/skeleton/hand/left",
+ "side": "left",
+ "binding_image_point": [ 5, 35 ]
+
+ },
+ "/input/skeleton/right": {
+ "type": "skeleton",
+ "skeleton": "/skeleton/hand/right",
+ "side": "right",
+ "binding_image_point": [ 5, 35 ]
+ },
+
+ "/output/haptic": {
+ "type": "vibration",
+ "binding_image_point": [ 5, 35 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/driver/slimevr/resources/input/slimevr_controller_bindings_legacy.json b/driver/slimevr/resources/input/slimevr_controller_bindings_legacy.json
new file mode 100644
index 00000000..487766bf
--- /dev/null
+++ b/driver/slimevr/resources/input/slimevr_controller_bindings_legacy.json
@@ -0,0 +1,24 @@
+{
+ "bindings": {
+ "/actions/legacy": {
+ "haptics": [
+ ],
+ "poses": [
+ {
+ "output": "/actions/legacy/in/Left_Pose",
+ "path": "/user/hand/left/pose/raw"
+ },
+ {
+ "output": "/actions/legacy/in/Right_Pose",
+ "path": "/user/hand/right/pose/raw"
+ }
+ ],
+ "sources": [
+ ]
+ }
+ },
+ "controller_type": "slimevr_virtual_controller",
+ "description": "Default binding values for legacy apps using the soft knuckles controller",
+ "name": "SlimeVR Legacy Defaults",
+ "simulated_actions": []
+}
\ No newline at end of file
diff --git a/driver/slimevr/resources/input/vrcompositor_bindings_slimevr.json b/driver/slimevr/resources/input/vrcompositor_bindings_slimevr.json
new file mode 100644
index 00000000..ef9e0bf9
--- /dev/null
+++ b/driver/slimevr/resources/input/vrcompositor_bindings_slimevr.json
@@ -0,0 +1,303 @@
+{
+ "action_manifest_version": 1,
+ "controller_type": "slimevr_virtual_controller",
+ "interaction_profile": "/interaction_profiles/valve/index_controller",
+ "description": "SlimeVR virtual controller",
+ "app_key": "openvr.component.vrcompositor",
+ "bindings": {
+ "/actions/lasermouse": {
+ "poses": [
+ {
+ "output": "/actions/lasermouse/in/Pointer",
+ "path": "/user/hand/left/pose/tip"
+ },
+ {
+ "output": "/actions/lasermouse/in/Pointer",
+ "path": "/user/hand/right/pose/tip"
+ }
+ ],
+ "haptics": [
+ {
+ "output": "/actions/lasermouse/out/Haptic",
+ "path": "/user/hand/left/output/haptic"
+ },
+ {
+ "output": "/actions/lasermouse/out/Haptic",
+ "path": "/user/hand/right/output/haptic"
+ }
+ ],
+ "sources": [
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse/in/LeftClick" } },
+ "mode": "button",
+ "parameters": {
+ "click_activate_threshold": "0.65",
+ "click_deactivate_threshold": "0.6"
+ },
+ "path": "/user/hand/left/input/trigger"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse/in/LeftClick" } },
+ "mode": "button",
+ "parameters": {
+ "click_activate_threshold": "0.65",
+ "click_deactivate_threshold": "0.6"
+ },
+ "path": "/user/hand/right/input/trigger"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse/in/Back" } },
+ "mode": "button",
+ "path": "/user/hand/left/input/b"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse/in/Back" } },
+ "mode": "button",
+ "path": "/user/hand/right/input/b"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse/in/Home" } },
+ "mode": "button",
+ "path": "/user/hand/left/input/a"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse/in/Home" } },
+ "mode": "button",
+ "path": "/user/hand/right/input/a"
+ },
+ {
+ "inputs": {
+ "position": { "output": "/actions/lasermouse/in/TrackpadValue" },
+ "touch": { "output": "/actions/lasermouse/in/TrackpadTouch" }
+ },
+ "mode": "trackpad",
+ "path": "/user/hand/left/input/trackpad"
+ },
+ {
+ "inputs": {
+ "position": { "output": "/actions/lasermouse/in/TrackpadValue" },
+ "touch": { "output": "/actions/lasermouse/in/TrackpadTouch" }
+ },
+ "mode": "trackpad",
+ "path": "/user/hand/right/input/trackpad"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse/in/MiddleClick" } },
+ "mode": "joystick",
+ "path": "/user/hand/left/input/joystick"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse/in/MiddleClick" } },
+ "mode": "joystick",
+ "path": "/user/hand/right/input/joystick"
+ }
+ ]
+ },
+ "/actions/lasermouse_secondary": {
+ "sources": [
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse_secondary/in/SwitchLaserHand" } },
+ "mode": "button",
+ "path": "/user/hand/left/input/trigger"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/lasermouse_secondary/in/SwitchLaserHand" } },
+ "mode": "button",
+ "path": "/user/hand/right/input/trigger"
+ }
+ ]
+ },
+ "/actions/scroll_discrete": {
+ "sources": [
+ {
+ "inputs": { "scroll": { "output": "/actions/scroll_discrete/in/Scroll" } },
+ "mode": "scroll",
+ "parameters": { "scroll_mode": "discrete" },
+ "path": "/user/hand/left/input/joystick"
+ },
+ {
+ "inputs": { "scroll": { "output": "/actions/scroll_discrete/in/Scroll" } },
+ "mode": "scroll",
+ "parameters": { "scroll_mode": "discrete" },
+ "path": "/user/hand/right/input/joystick"
+ },
+ {
+ "inputs": { "scroll": { "output": "/actions/scroll_discrete/in/Scroll" } },
+ "mode": "scroll",
+ "parameters": {
+ "scroll_mode": "discrete",
+ "discrete_scroll_trackpad_accumthreshold_onmove": "0.28",
+ "discrete_scroll_trackpad_accumthreshold_onreversal": "0.84",
+ "discrete_scroll_trackpad_accumthreshold_ontouch": "0.78",
+ "discrete_scroll_trackpad_noisethreshold_onmove": "0.01",
+ "discrete_scroll_trackpad_noisethreshold_onreversal": "0.045",
+ "discrete_scroll_trackpad_noisethreshold_ontouch": "0.15",
+ "discrete_scroll_trackpad_slideandhold_borderbottom": "-0.65",
+ "discrete_scroll_trackpad_slideandhold_bordertop": "0.55"
+ },
+ "path": "/user/hand/left/input/trackpad"
+ },
+ {
+ "inputs": { "scroll": { "output": "/actions/scroll_discrete/in/Scroll" } },
+ "mode": "scroll",
+ "parameters": {
+ "scroll_mode": "discrete",
+ "discrete_scroll_trackpad_accumthreshold_onmove": "0.28",
+ "discrete_scroll_trackpad_accumthreshold_onreversal": "0.84",
+ "discrete_scroll_trackpad_accumthreshold_ontouch": "0.78",
+ "discrete_scroll_trackpad_noisethreshold_onmove": "0.01",
+ "discrete_scroll_trackpad_noisethreshold_onreversal": "0.045",
+ "discrete_scroll_trackpad_noisethreshold_ontouch": "0.15",
+ "discrete_scroll_trackpad_slideandhold_borderbottom": "-0.65",
+ "discrete_scroll_trackpad_slideandhold_bordertop": "0.55"
+ },
+ "path": "/user/hand/right/input/trackpad"
+ }
+ ]
+ },
+ "/actions/scroll_smooth": {
+ "sources": [
+ {
+ "inputs": { "scroll": { "output": "/actions/scroll_smooth/in/Scroll" } },
+ "mode": "scroll",
+ "parameters": { "scroll_mode": "smooth" },
+ "path": "/user/hand/left/input/joystick"
+ },
+ {
+ "inputs": { "scroll": { "output": "/actions/scroll_smooth/in/Scroll" } },
+ "mode": "scroll",
+ "parameters": { "scroll_mode": "smooth" },
+ "path": "/user/hand/right/input/joystick"
+ },
+ {
+ "inputs": { "scroll": { "output": "/actions/scroll_smooth/in/Scroll" } },
+ "mode": "scroll",
+ "parameters": { "scroll_mode": "smooth" },
+ "path": "/user/hand/left/input/trackpad"
+ },
+ {
+ "inputs": { "scroll": { "output": "/actions/scroll_smooth/in/Scroll" } },
+ "mode": "scroll",
+ "parameters": { "scroll_mode": "smooth" },
+ "path": "/user/hand/right/input/trackpad"
+ }
+ ]
+ },
+ "/actions/system": {
+ "chords": [
+ {
+ "inputs": [
+ [ "/user/hand/left/input/system", "click" ],
+ [ "/user/hand/left/input/trigger", "click" ]
+ ],
+ "output": "/actions/system/in/TakeScreenshot"
+ },
+ {
+ "inputs": [
+ [ "/user/hand/right/input/system", "click" ],
+ [ "/user/hand/right/input/trigger", "click" ]
+ ],
+ "output": "/actions/system/in/TakeScreenshot"
+ }
+ ],
+ "sources": [
+ {
+ "inputs": {
+ "click": { "output": "/actions/system/in/ToggleDashboard" },
+ "double": { "output": "/actions/system/in/ToggleRoomView" },
+ "held": { "output": "/actions/system/in/SystemButtonChord" }
+ },
+ "mode": "button",
+ "path": "/user/hand/left/input/system"
+ },
+ {
+ "inputs": {
+ "click": { "output": "/actions/system/in/ToggleDashboard" },
+ "double": { "output": "/actions/system/in/ToggleRoomView" },
+ "held": { "output": "/actions/system/in/SystemButtonChord" }
+ },
+ "mode": "button",
+ "path": "/user/hand/right/input/system"
+ }
+ ]
+ },
+ "/actions/quickrecenter": {
+ "sources": [
+ {
+ "inputs": { "long": { "output": "/actions/quickrecenter/in/Recenter" } },
+ "parameters": {
+ "long_press_delay": 1.0,
+ "long_press_expiry": 3.0
+ },
+ "mode": "button",
+ "path": "/user/hand/left/input/system"
+ },
+ {
+ "inputs": { "long": { "output": "/actions/quickrecenter/in/Recenter" } },
+ "parameters": {
+ "long_press_delay": 1.0,
+ "long_press_expiry": 3.0
+ },
+ "mode": "button",
+ "path": "/user/hand/right/input/system"
+ }
+ ]
+ },
+ "/actions/roomsetup_floor": {
+ "sources": [
+ {
+ "inputs": { "position": { "output": "/actions/roomsetup_floor/in/FloorFineAdjust" } },
+ "mode": "joystick",
+ "path": "/user/hand/left/input/joystick"
+ },
+ {
+ "inputs": { "position": { "output": "/actions/roomsetup_floor/in/FloorFineAdjust" } },
+ "mode": "joystick",
+ "path": "/user/hand/right/input/joystick"
+ }
+ ]
+ },
+ "/actions/dualanalog": {
+ "sources": [
+ {
+ "inputs": { "click": { "output": "/actions/dualanalog/in/ModeSwitch1" } },
+ "mode": "button",
+ "path": "/user/hand/left/input/a"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/dualanalog/in/ModeSwitch2" } },
+ "mode": "button",
+ "path": "/user/hand/right/input/a"
+ },
+ {
+ "inputs": {
+ "click": { "output": "/actions/dualanalog/in/LeftClick" },
+ "position": { "output": "/actions/dualanalog/in/LeftValue" },
+ "touch": { "output": "/actions/dualanalog/in/LeftTouch" }
+ },
+ "mode": "joystick",
+ "path": "/user/hand/left/input/joystick"
+ },
+ {
+ "inputs": {
+ "click": { "output": "/actions/dualanalog/in/RightClick" },
+ "position": { "output": "/actions/dualanalog/in/RightValue" },
+ "touch": { "output": "/actions/dualanalog/in/RightTouch" }
+ },
+ "mode": "joystick",
+ "path": "/user/hand/right/input/joystick"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/dualanalog/in/LeftClick" } },
+ "mode": "button",
+ "path": "/user/hand/left/input/trigger"
+ },
+ {
+ "inputs": { "click": { "output": "/actions/dualanalog/in/RightClick" } },
+ "mode": "button",
+ "path": "/user/hand/right/input/trigger"
+ }
+ ]
+ }
+ }
+}
diff --git a/driver/slimevr/resources/localization/localization.json b/driver/slimevr/resources/localization/localization.json
index 72b69804..e46bac8e 100644
--- a/driver/slimevr/resources/localization/localization.json
+++ b/driver/slimevr/resources/localization/localization.json
@@ -1,16 +1,16 @@
[
{
"language_tag": "en_US",
- "example_controller": "Example Controller",
- "example_tracker": "Example Tracker",
- "example_basestation": "Example BaseStation",
+ "slimevr_virtual_controller": "SlimeVR Virtual Controller",
+ "/input/double_tap": "Double tap",
+ "/input/triple_tap": "Triple tap",
"/input/a": "A Button",
"/input/b": "B Button",
- "/input/system": "System Button",
- "/input/trackpad": "Trackpad",
- "/input/joystick": "Joystick",
- "/input/skeleton": "Skeleton",
- "/input/trigger": "Trigger",
- "/output/haptic": "Haptic"
+ "/input/pinch": "Pinch Gesture",
+ "/input/finger/index": "Index Finger",
+ "/input/finger/middle": "Middle Finger",
+ "/input/finger/ring": "Ring Finger",
+ "/input/finger/pinky": "Pinky Finger",
+ "/input/thumbstick": "Thumb Stick"
}
]
diff --git a/driver/slimevr/resources/rendermodels/example_controller/example_controller.fbx b/driver/slimevr/resources/rendermodels/example_controller/example_controller.fbx
deleted file mode 100644
index 9ab31054..00000000
Binary files a/driver/slimevr/resources/rendermodels/example_controller/example_controller.fbx and /dev/null differ
diff --git a/driver/slimevr/resources/rendermodels/example_controller/example_controller.mtl b/driver/slimevr/resources/rendermodels/example_controller/example_controller.mtl
deleted file mode 100644
index 08f028cc..00000000
--- a/driver/slimevr/resources/rendermodels/example_controller/example_controller.mtl
+++ /dev/null
@@ -1,13 +0,0 @@
-# Blender MTL File: 'None'
-# Material Count: 1
-
-newmtl Material_75
-Ns 900.000000
-Ka 1.000000 1.000000 1.000000
-Kd 0.498039 0.498039 0.498039
-Ks 1.000000 1.000000 1.000000
-Ke 0.000000 0.000000 0.000000
-Ni 1.450000
-d 1.000000
-illum 3
-map_Kd example_controller.png
diff --git a/driver/slimevr/resources/rendermodels/example_controller/example_controller.obj b/driver/slimevr/resources/rendermodels/example_controller/example_controller.obj
deleted file mode 100644
index 9d65a72a..00000000
--- a/driver/slimevr/resources/rendermodels/example_controller/example_controller.obj
+++ /dev/null
@@ -1,969 +0,0 @@
-# Blender v2.81 (sub 16) OBJ File: ''
-# www.blender.org
-mtllib example_controller.mtl
-usemtl Material_75
-v -0.032265 -0.032265 0.032265
-v -0.032265 0.032265 0.032265
-v -0.032265 0.032265 -0.032265
-v -0.032265 -0.032265 0.032265
-v -0.032265 0.032265 -0.032265
-v -0.032265 -0.032265 -0.032265
-v -0.032265 0.032265 -0.032265
-v 0.032265 0.032265 -0.032265
-v 0.016133 0.016133 -0.032265
-v -0.016133 0.016133 -0.032265
-v 0.032265 0.032265 0.032265
-v 0.032265 -0.032265 0.032265
-v 0.032265 -0.016133 0.016133
-v 0.032265 0.016133 0.016133
-v 0.032265 -0.032265 0.032265
-v 0.032265 0.032265 0.032265
-v -0.032265 0.032265 0.032265
-v -0.032265 -0.032265 0.032265
-v -0.032265 -0.032265 -0.032265
-v 0.032265 -0.032265 -0.032265
-v 0.032265 -0.032265 0.032265
-v -0.032265 -0.032265 0.032265
-v 0.032265 0.032265 -0.032265
-v -0.032265 0.032265 -0.032265
-v -0.016133 0.032265 -0.016133
-v 0.016133 0.032265 -0.016133
-v -0.016133 0.016133 -0.032265
-v 0.016133 0.016133 -0.032265
-v 0.028397 0.028398 -0.113590
-v -0.016133 0.016133 -0.032265
-v 0.028397 0.028398 -0.113590
-v -0.028398 0.028398 -0.113590
-v -0.032265 -0.032265 -0.032265
-v -0.016133 -0.016133 -0.032265
-v 0.032265 0.032265 -0.032265
-v 0.032265 -0.032265 -0.032265
-v 0.016133 -0.016133 -0.032265
-v 0.016133 0.016133 -0.032265
-v -0.032265 -0.032265 -0.032265
-v -0.016133 -0.016133 -0.032265
-v 0.032265 -0.016133 -0.016133
-v 0.032265 0.016133 -0.016133
-v 0.113590 0.028398 -0.028397
-v 0.032265 -0.016133 -0.016133
-v 0.113590 0.028398 -0.028397
-v 0.113590 -0.028397 -0.028398
-v 0.032265 -0.032265 0.032265
-v 0.032265 -0.032265 -0.032265
-v 0.032265 -0.016133 -0.016133
-v 0.032265 -0.016133 0.016133
-v 0.032265 0.032265 -0.032265
-v 0.032265 0.016133 -0.016133
-v 0.032265 0.032265 -0.032265
-v 0.032265 0.016133 -0.016133
-v -0.016133 0.032265 0.016133
-v 0.016133 0.032265 0.016133
-v 0.028397 0.113590 0.028398
-v -0.016133 0.032265 0.016133
-v 0.028397 0.113590 0.028398
-v -0.028398 0.113590 0.028398
-v 0.032265 0.032265 0.032265
-v 0.016133 0.032265 0.016133
-v -0.032265 0.032265 -0.032265
-v -0.032265 0.032265 0.032265
-v -0.016133 0.032265 0.016133
-v -0.016133 0.032265 -0.016133
-v 0.032265 0.032265 0.032265
-v 0.016133 0.032265 0.016133
-v 0.028397 0.113590 -0.028397
-v -0.028398 0.113590 -0.028397
-v -0.028398 0.113590 0.028398
-v 0.028397 0.113590 0.028398
-v 0.016133 0.032265 -0.016133
-v -0.016133 0.032265 -0.016133
-v -0.028398 0.113590 -0.028397
-v 0.016133 0.032265 -0.016133
-v -0.028398 0.113590 -0.028397
-v 0.028397 0.113590 -0.028397
-v 0.016133 0.032265 0.016133
-v 0.016133 0.032265 -0.016133
-v 0.028397 0.113590 -0.028397
-v 0.016133 0.032265 0.016133
-v 0.028397 0.113590 -0.028397
-v 0.028397 0.113590 0.028398
-v -0.016133 0.032265 -0.016133
-v -0.016133 0.032265 0.016133
-v -0.028398 0.113590 0.028398
-v -0.016133 0.032265 -0.016133
-v -0.028398 0.113590 0.028398
-v -0.028398 0.113590 -0.028397
-v 0.113590 -0.028397 -0.028398
-v 0.113590 0.028398 -0.028397
-v 0.113590 0.028397 0.028398
-v 0.113590 -0.028398 0.028397
-v 0.032265 0.016133 0.016133
-v 0.032265 -0.016133 0.016133
-v 0.113590 -0.028398 0.028397
-v 0.032265 0.016133 0.016133
-v 0.113590 -0.028398 0.028397
-v 0.113590 0.028397 0.028398
-v 0.032265 -0.016133 0.016133
-v 0.032265 -0.016133 -0.016133
-v 0.113590 -0.028397 -0.028398
-v 0.032265 -0.016133 0.016133
-v 0.113590 -0.028397 -0.028398
-v 0.113590 -0.028398 0.028397
-v 0.032265 0.016133 -0.016133
-v 0.032265 0.016133 0.016133
-v 0.113590 0.028397 0.028398
-v 0.032265 0.016133 -0.016133
-v 0.113590 0.028397 0.028398
-v 0.113590 0.028398 -0.028397
-v -0.028398 -0.028397 -0.113590
-v -0.028398 0.028398 -0.113590
-v 0.028397 0.028398 -0.113590
-v -0.028398 -0.028397 -0.113590
-v 0.028397 0.028398 -0.113590
-v 0.028397 -0.028397 -0.113590
-v -0.016133 -0.016133 -0.032265
-v -0.016133 0.016133 -0.032265
-v -0.028398 0.028398 -0.113590
-v -0.016133 -0.016133 -0.032265
-v -0.028398 0.028398 -0.113590
-v -0.028398 -0.028397 -0.113590
-v 0.016133 0.016133 -0.032265
-v 0.016133 -0.016133 -0.032265
-v 0.028397 -0.028397 -0.113590
-v 0.016133 0.016133 -0.032265
-v 0.028397 -0.028397 -0.113590
-v 0.028397 0.028398 -0.113590
-v 0.016133 -0.016133 -0.032265
-v -0.016133 -0.016133 -0.032265
-v -0.028398 -0.028397 -0.113590
-v 0.016133 -0.016133 -0.032265
-v -0.028398 -0.028397 -0.113590
-v 0.028397 -0.028397 -0.113590
-v -0.001752 0.002383 -0.121335
-v -0.014217 0.018198 -0.121335
-v -0.008403 0.018198 -0.121335
-v -0.001752 0.002383 -0.121335
-v -0.008403 0.018198 -0.121335
-v 0.000612 0.006620 -0.121335
-v 0.009727 0.018198 -0.121335
-v 0.015540 0.018198 -0.121335
-v 0.003076 0.002334 -0.121335
-v -0.001752 -0.015403 -0.121335
-v -0.001752 0.002383 -0.121335
-v 0.003076 0.002334 -0.121335
-v 0.003076 -0.015403 -0.121335
-v -0.001752 0.002383 -0.105327
-v -0.008403 0.018198 -0.105327
-v -0.014217 0.018198 -0.105327
-v 0.000612 0.006620 -0.105327
-v 0.000612 0.006620 -0.105327
-v 0.015540 0.018198 -0.105327
-v 0.009727 0.018198 -0.105327
-v 0.003076 0.002334 -0.105327
-v -0.001752 0.002383 -0.105327
-v -0.001752 -0.015403 -0.105327
-v 0.003076 0.002334 -0.105327
-v -0.001752 0.002383 -0.105327
-v -0.001752 -0.015403 -0.105327
-v 0.003076 -0.015403 -0.105327
-v 0.003076 0.002334 -0.105327
-v 0.003076 0.002334 -0.121335
-v 0.015540 0.018198 -0.121335
-v 0.015540 0.018198 -0.105327
-v 0.003076 0.002334 -0.105327
-v -0.001752 -0.015403 -0.121335
-v 0.003076 -0.015403 -0.121335
-v 0.003076 -0.015403 -0.105327
-v -0.001752 -0.015403 -0.105327
-v -0.014217 0.018198 -0.121335
-v -0.001752 0.002383 -0.121335
-v -0.001752 0.002383 -0.105327
-v -0.014217 0.018198 -0.105327
-v 0.015540 0.018198 -0.121335
-v 0.009727 0.018198 -0.121335
-v 0.009727 0.018198 -0.105327
-v 0.015540 0.018198 -0.121335
-v 0.009727 0.018198 -0.105327
-v 0.015540 0.018198 -0.105327
-v 0.003076 -0.015403 -0.121335
-v 0.003076 0.002334 -0.121335
-v 0.003076 0.002334 -0.105327
-v 0.003076 -0.015403 -0.121335
-v 0.003076 0.002334 -0.105327
-v 0.003076 -0.015403 -0.105327
-v 0.000612 0.006620 -0.121335
-v -0.008403 0.018198 -0.121335
-v -0.008403 0.018198 -0.105327
-v 0.000612 0.006620 -0.105327
-v -0.001752 0.002383 -0.121335
-v -0.001752 -0.015403 -0.121335
-v -0.001752 -0.015403 -0.105327
-v -0.001752 0.002383 -0.121335
-v -0.001752 -0.015403 -0.105327
-v -0.001752 0.002383 -0.105327
-v 0.009727 0.018198 -0.121335
-v 0.000612 0.006620 -0.121335
-v 0.000612 0.006620 -0.105327
-v 0.009727 0.018198 -0.121335
-v 0.000612 0.006620 -0.105327
-v 0.009727 0.018198 -0.105327
-v -0.008403 0.018198 -0.121335
-v -0.014217 0.018198 -0.121335
-v -0.014217 0.018198 -0.105327
-v -0.008403 0.018198 -0.121335
-v -0.014217 0.018198 -0.105327
-v -0.008403 0.018198 -0.105327
-v 0.121335 0.001989 -0.001398
-v 0.121335 0.018198 -0.014848
-v 0.121335 0.018198 -0.008985
-v 0.121335 0.005388 0.001558
-v 0.121335 0.018198 0.012052
-v 0.121335 0.018198 0.017866
-v 0.121335 0.005388 0.001558
-v 0.121335 0.018198 0.017866
-v 0.121335 0.001989 0.004465
-v 0.121335 0.001989 0.004465
-v 0.121335 -0.015403 -0.015882
-v 0.121335 -0.001509 0.001558
-v 0.121335 -0.001509 0.001558
-v 0.121335 -0.015403 0.018802
-v 0.121335 -0.015403 -0.010019
-v 0.121335 -0.015403 0.012988
-v 0.121335 -0.015403 0.018802
-v 0.104411 0.001989 -0.001398
-v 0.104411 0.018198 -0.008985
-v 0.104411 0.018198 -0.014848
-v 0.104411 0.005388 0.001558
-v 0.104411 0.005388 0.001558
-v 0.104411 0.018198 0.017866
-v 0.104411 0.018198 0.012052
-v 0.104411 0.001989 0.004465
-v 0.104411 0.018198 0.017866
-v 0.104411 -0.015403 -0.015882
-v 0.104411 -0.001509 0.001558
-v 0.104411 -0.015403 0.018802
-v 0.104411 -0.015403 -0.015882
-v 0.104411 -0.015403 -0.010019
-v 0.104411 -0.001509 0.001558
-v 0.104411 -0.015403 0.012988
-v 0.104411 -0.015403 0.018802
-v 0.121335 -0.015403 0.012988
-v 0.121335 -0.015403 0.018802
-v 0.104411 -0.015403 0.018802
-v 0.104411 -0.015403 0.012988
-v 0.121335 0.018198 -0.014848
-v 0.121335 0.001989 -0.001398
-v 0.104411 0.001989 -0.001398
-v 0.121335 0.018198 -0.014848
-v 0.104411 0.001989 -0.001398
-v 0.104411 0.018198 -0.014848
-v 0.121335 -0.015403 -0.015882
-v 0.121335 -0.015403 -0.010019
-v 0.104411 -0.015403 -0.010019
-v 0.104411 -0.015403 -0.015882
-v 0.121335 0.018198 0.017866
-v 0.121335 0.018198 0.012052
-v 0.104411 0.018198 0.012052
-v 0.104411 0.018198 0.017866
-v 0.121335 0.005388 0.001558
-v 0.121335 0.018198 -0.008985
-v 0.104411 0.018198 -0.008985
-v 0.121335 0.005388 0.001558
-v 0.104411 0.018198 -0.008985
-v 0.104411 0.005388 0.001558
-v 0.121335 0.001989 -0.001398
-v 0.121335 -0.015403 -0.015882
-v 0.104411 -0.015403 -0.015882
-v 0.121335 0.001989 -0.001398
-v 0.104411 -0.015403 -0.015882
-v 0.104411 0.001989 -0.001398
-v 0.121335 -0.015403 -0.010019
-v 0.121335 -0.001509 0.001558
-v 0.104411 -0.001509 0.001558
-v 0.121335 -0.015403 -0.010019
-v 0.104411 -0.001509 0.001558
-v 0.104411 -0.015403 -0.010019
-v 0.121335 0.018198 0.012052
-v 0.121335 0.005388 0.001558
-v 0.104411 0.005388 0.001558
-v 0.104411 0.018198 0.012052
-v 0.121335 -0.001509 0.001558
-v 0.121335 -0.015403 0.012988
-v 0.104411 -0.015403 0.012988
-v 0.104411 -0.001509 0.001558
-v 0.121335 0.018198 -0.008985
-v 0.121335 0.018198 -0.014848
-v 0.104411 0.018198 -0.014848
-v 0.104411 0.018198 -0.008985
-v 0.121335 0.001989 0.004465
-v 0.121335 0.018198 0.017866
-v 0.104411 0.018198 0.017866
-v 0.104411 0.001989 0.004465
-v 0.121335 -0.015403 0.018802
-v 0.121335 0.001989 0.004465
-v 0.104411 0.001989 0.004465
-v 0.104411 -0.015403 0.018802
-v -0.013862 0.121335 0.014442
-v -0.018198 0.121335 0.014442
-v -0.018198 0.121335 -0.014527
-v -0.013862 0.121335 0.014442
-v -0.018198 0.121335 -0.014527
-v -0.013862 0.121335 -0.005610
-v -0.013862 0.121335 -0.005610
-v -0.018198 0.121335 -0.014527
-v 0.011067 0.121335 0.006855
-v 0.015403 0.121335 0.015772
-v -0.013862 0.121335 -0.005610
-v 0.011067 0.121335 0.006855
-v 0.015403 0.121335 0.015772
-v 0.011067 0.121335 0.006855
-v 0.011067 0.121335 -0.014527
-v 0.015403 0.121335 0.015772
-v 0.011067 0.121335 -0.014527
-v 0.015403 0.121335 -0.014527
-v -0.013862 0.098929 0.014442
-v -0.018198 0.098929 -0.014527
-v -0.018198 0.098929 0.014442
-v -0.013862 0.098929 0.014442
-v -0.013862 0.098929 -0.005610
-v -0.018198 0.098929 -0.014527
-v -0.013862 0.098929 -0.005610
-v 0.011067 0.098929 0.006854
-v -0.018198 0.098929 -0.014527
-v 0.015403 0.098929 0.015772
-v 0.011067 0.098929 0.006854
-v -0.013862 0.098929 -0.005610
-v 0.015403 0.098929 0.015772
-v 0.011067 0.098929 -0.014527
-v 0.011067 0.098929 0.006854
-v 0.015403 0.098929 0.015772
-v 0.015403 0.098929 -0.014527
-v 0.011067 0.098929 -0.014527
-v -0.018198 0.121335 -0.014527
-v -0.018198 0.121335 0.014442
-v -0.018198 0.098929 0.014442
-v -0.018198 0.121335 -0.014527
-v -0.018198 0.098929 0.014442
-v -0.018198 0.098929 -0.014527
-v 0.015403 0.121335 0.015772
-v 0.015403 0.121335 -0.014527
-v 0.015403 0.098929 -0.014527
-v 0.015403 0.121335 0.015772
-v 0.015403 0.098929 -0.014527
-v 0.015403 0.098929 0.015772
-v -0.018198 0.121335 0.014442
-v -0.013862 0.121335 0.014442
-v -0.013862 0.098929 0.014442
-v -0.018198 0.121335 0.014442
-v -0.013862 0.098929 0.014442
-v -0.018198 0.098929 0.014442
-v -0.013862 0.121335 -0.005610
-v 0.015403 0.121335 0.015772
-v 0.015403 0.098929 0.015772
-v -0.013862 0.121335 -0.005610
-v 0.015403 0.098929 0.015772
-v -0.013862 0.098929 -0.005610
-v -0.013862 0.121335 0.014442
-v -0.013862 0.121335 -0.005610
-v -0.013862 0.098929 -0.005610
-v -0.013862 0.121335 0.014442
-v -0.013862 0.098929 -0.005610
-v -0.013862 0.098929 0.014442
-v 0.015403 0.121335 -0.014527
-v 0.011067 0.121335 -0.014527
-v 0.011067 0.098929 -0.014527
-v 0.015403 0.121335 -0.014527
-v 0.011067 0.098929 -0.014527
-v 0.015403 0.098929 -0.014527
-v 0.011067 0.121335 -0.014527
-v 0.011067 0.121335 0.006855
-v 0.011067 0.098929 0.006854
-v 0.011067 0.121335 -0.014527
-v 0.011067 0.098929 0.006854
-v 0.011067 0.098929 -0.014527
-v 0.011067 0.121335 0.006855
-v -0.018198 0.121335 -0.014527
-v -0.018198 0.098929 -0.014527
-v 0.011067 0.121335 0.006855
-v -0.018198 0.098929 -0.014527
-v 0.011067 0.098929 0.006854
-vt 0.877679 0.999989
-vt 0.704361 0.862110
-vt 0.623368 0.995214
-vt 0.549066 0.593772
-vt 0.632255 0.405504
-vt 0.427163 0.450286
-vt 0.590790 0.226030
-vt 0.503462 0.345266
-vt 0.575617 0.338540
-vt 0.634145 0.279193
-vt 0.701116 0.660331
-vt 0.935436 0.738186
-vt 0.886693 0.651541
-vt 0.824282 0.608270
-vt 0.935436 0.738186
-vt 0.701116 0.660331
-vt 0.704361 0.862110
-vt 0.877679 0.999989
-vt 0.427163 0.450286
-vt 0.264037 0.546895
-vt 0.357144 0.682415
-vt 0.549066 0.593772
-vt 0.503462 0.345266
-vt 0.590790 0.226030
-vt 0.517346 0.232950
-vt 0.465179 0.299295
-vt 0.634145 0.279193
-vt 0.575617 0.338540
-vt 0.811297 0.524319
-vt 0.634145 0.279193
-vt 0.811297 0.524319
-vt 0.815519 0.389933
-vt 0.730237 0.173993
-vt 0.711091 0.223332
-vt 0.134165 0.525760
-vt 0.264037 0.546895
-vt 0.257518 0.467648
-vt 0.170177 0.468780
-vt 0.427163 0.450286
-vt 0.343750 0.438493
-vt 0.221377 0.611101
-vt 0.148490 0.590709
-vt 0.028295 0.807064
-vt 0.221377 0.611101
-vt 0.028295 0.807064
-vt 0.123704 0.829921
-vt 0.357144 0.682415
-vt 0.264037 0.546895
-vt 0.221377 0.611101
-vt 0.281763 0.683253
-vt 0.770178 0.550866
-vt 0.792445 0.563409
-vt 0.134165 0.525760
-vt 0.148490 0.590709
-vt 0.637580 0.876969
-vt 0.644000 0.708063
-vt 0.442150 0.778258
-vt 0.637580 0.876969
-vt 0.442150 0.778258
-vt 0.412653 0.876915
-vt 0.355897 0.400451
-vt 0.390530 0.348321
-vt 0.623368 0.995214
-vt 0.704361 0.862110
-vt 0.637580 0.876969
-vt 0.598416 0.970365
-vt 0.701116 0.660331
-vt 0.644000 0.708063
-vt 0.277279 0.702623
-vt 0.301656 0.977486
-vt 0.412653 0.876915
-vt 0.442150 0.778258
-vt 0.465179 0.299295
-vt 0.517346 0.232950
-vt 0.338533 0.075582
-vt 0.465179 0.299295
-vt 0.338533 0.075582
-vt 0.261104 0.194327
-vt 0.390530 0.348321
-vt 0.465179 0.299295
-vt 0.261104 0.194327
-vt 0.644000 0.708063
-vt 0.277279 0.702623
-vt 0.442150 0.778258
-vt 0.598416 0.970365
-vt 0.637580 0.876969
-vt 0.412653 0.876915
-vt 0.598416 0.970365
-vt 0.412653 0.876915
-vt 0.301656 0.977486
-vt 0.123704 0.829921
-vt 0.028295 0.807064
-vt 0.000207 0.999902
-vt 0.179879 0.922875
-vt 0.824282 0.608270
-vt 0.886693 0.651541
-vt 0.999978 0.528948
-vt 0.824282 0.608270
-vt 0.999978 0.528948
-vt 0.955080 0.461952
-vt 0.281763 0.683253
-vt 0.221377 0.611101
-vt 0.123704 0.829921
-vt 0.281763 0.683253
-vt 0.123704 0.829921
-vt 0.179879 0.922875
-vt 0.792445 0.563409
-vt 0.824282 0.608270
-vt 0.955080 0.461952
-vt 0.792445 0.563409
-vt 0.955080 0.461952
-vt 0.922398 0.350776
-vt 0.923406 0.337617
-vt 0.815519 0.389933
-vt 0.811297 0.524319
-vt 0.296472 0.242822
-vt 0.016945 0.256359
-vt 0.190705 0.312874
-vt 0.711091 0.223332
-vt 0.634145 0.279193
-vt 0.815519 0.389933
-vt 0.711091 0.223332
-vt 0.815519 0.389933
-vt 0.923406 0.337617
-vt 0.170177 0.468780
-vt 0.257518 0.467648
-vt 0.190705 0.312874
-vt 0.170177 0.468780
-vt 0.190705 0.312874
-vt 0.016945 0.256359
-vt 0.257518 0.467648
-vt 0.343750 0.438493
-vt 0.296472 0.242822
-vt 0.257518 0.467648
-vt 0.296472 0.242822
-vt 0.190705 0.312874
-vt 0.164647 0.206490
-vt 0.137409 0.237522
-vt 0.148149 0.246926
-vt 0.879209 0.162218
-vt 0.899789 0.136235
-vt 0.866618 0.153517
-vt 0.833631 0.180137
-vt 0.834320 0.192690
-vt 0.873454 0.165845
-vt 0.630495 0.206194
-vt 0.664214 0.194627
-vt 0.663560 0.184439
-vt 0.626594 0.192788
-vt 0.837231 0.112168
-vt 0.876068 0.106100
-vt 0.884365 0.090995
-vt 0.841157 0.123918
-vt 0.665576 0.126268
-vt 0.701825 0.140263
-vt 0.706253 0.126675
-vt 0.660674 0.137415
-vt 0.651633 0.130906
-vt 0.114167 0.149049
-vt 0.100911 0.183177
-vt 0.110940 0.184971
-vt 0.947862 0.768248
-vt 0.959170 0.761371
-vt 0.952077 0.721815
-vt 0.663560 0.184439
-vt 0.728611 0.170526
-vt 0.701825 0.140263
-vt 0.660674 0.137415
-vt 0.993131 0.781178
-vt 0.997378 0.766008
-vt 0.959170 0.761371
-vt 0.947862 0.768248
-vt 0.137409 0.237522
-vt 0.164647 0.206490
-vt 0.110940 0.184971
-vt 0.100277 0.249077
-vt 0.834320 0.192690
-vt 0.833631 0.180137
-vt 0.803714 0.208463
-vt 0.728611 0.170526
-vt 0.706253 0.126675
-vt 0.701825 0.140263
-vt 0.626594 0.192788
-vt 0.663560 0.184439
-vt 0.660674 0.137415
-vt 0.997378 0.766008
-vt 0.952077 0.721815
-vt 0.959170 0.761371
-vt 0.866618 0.153517
-vt 0.899789 0.136235
-vt 0.876068 0.106100
-vt 0.841157 0.123918
-vt 0.987367 0.825299
-vt 0.993131 0.781178
-vt 0.947862 0.768248
-vt 0.164647 0.206490
-vt 0.114167 0.149049
-vt 0.110940 0.184971
-vt 0.833631 0.180137
-vt 0.866618 0.153517
-vt 0.841157 0.123918
-vt 0.706666 0.085370
-vt 0.665576 0.126268
-vt 0.706253 0.126675
-vt 0.148149 0.246926
-vt 0.137409 0.237522
-vt 0.100277 0.249077
-vt 0.899789 0.136235
-vt 0.884365 0.090995
-vt 0.876068 0.106100
-vt 0.901844 0.186326
-vt 0.849583 0.192142
-vt 0.847732 0.210709
-vt 0.877454 0.230132
-vt 0.836064 0.250561
-vt 0.809560 0.270587
-vt 0.607617 0.129696
-vt 0.614474 0.075690
-vt 0.597435 0.137391
-vt 0.893705 0.253191
-vt 0.946146 0.202975
-vt 0.903446 0.248533
-vt 0.608804 0.139682
-vt 0.621541 0.196470
-vt 0.946562 0.220339
-vt 0.947342 0.347581
-vt 0.947818 0.371736
-vt 0.534464 0.174384
-vt 0.482487 0.138172
-vt 0.475490 0.146348
-vt 0.538361 0.155457
-vt 0.819571 0.227383
-vt 0.765189 0.248073
-vt 0.798662 0.247500
-vt 0.551825 0.161264
-vt 0.573868 0.099577
-vt 0.522006 0.217440
-vt 0.547894 0.171843
-vt 0.588497 0.222780
-vt 0.999982 0.156817
-vt 0.984586 0.205234
-vt 0.981163 0.309426
-vt 0.975856 0.340212
-vt 0.993906 0.360793
-vt 0.947342 0.347581
-vt 0.947818 0.371736
-vt 0.993906 0.360793
-vt 0.975856 0.340212
-vt 0.849583 0.192142
-vt 0.901844 0.186326
-vt 0.913311 0.138870
-vt 0.441102 0.160191
-vt 0.534464 0.174384
-vt 0.475490 0.146348
-vt 0.946146 0.202975
-vt 0.946562 0.220339
-vt 0.984586 0.205234
-vt 0.999982 0.156817
-vt 0.809560 0.270587
-vt 0.836064 0.250561
-vt 0.798662 0.247500
-vt 0.765189 0.248073
-vt 0.877454 0.230132
-vt 0.847732 0.210709
-vt 0.799992 0.211497
-vt 0.877454 0.230132
-vt 0.799992 0.211497
-vt 0.819571 0.227383
-vt 0.901844 0.186326
-vt 0.946146 0.202975
-vt 0.999982 0.156817
-vt 0.901844 0.186326
-vt 0.999982 0.156817
-vt 0.913311 0.138870
-vt 0.946562 0.220339
-vt 0.903446 0.248533
-vt 0.981163 0.309426
-vt 0.946562 0.220339
-vt 0.981163 0.309426
-vt 0.984586 0.205234
-vt 0.836064 0.250561
-vt 0.877454 0.230132
-vt 0.819571 0.227383
-vt 0.798662 0.247500
-vt 0.903446 0.248533
-vt 0.947342 0.347581
-vt 0.975856 0.340212
-vt 0.981163 0.309426
-vt 0.431246 0.134617
-vt 0.441102 0.160191
-vt 0.475490 0.146348
-vt 0.482487 0.138172
-vt 0.597435 0.137391
-vt 0.614474 0.075690
-vt 0.573868 0.099577
-vt 0.551825 0.161264
-vt 0.621541 0.196470
-vt 0.597435 0.137391
-vt 0.551825 0.161264
-vt 0.588497 0.222780
-vt 0.025660 0.167277
-vt 0.019460 0.184500
-vt 0.085457 0.197038
-vt 0.025660 0.167277
-vt 0.085457 0.197038
-vt 0.070813 0.179118
-vt 0.244895 0.177499
-vt 0.271388 0.170697
-vt 0.200499 0.139389
-vt 0.182349 0.154977
-vt 0.244895 0.177499
-vt 0.200499 0.139389
-vt 0.182349 0.154977
-vt 0.200499 0.139389
-vt 0.172554 0.101012
-vt 0.950882 0.695534
-vt 0.997250 0.630519
-vt 0.985549 0.630401
-vt 0.968010 0.702894
-vt 0.995974 0.639378
-vt 0.953135 0.699966
-vt 0.968010 0.702894
-vt 0.998149 0.664236
-vt 0.995974 0.639378
-vt 0.294567 0.089810
-vt 0.232375 0.081405
-vt 0.303833 0.115587
-vt 0.172240 0.213102
-vt 0.181083 0.238508
-vt 0.257933 0.241733
-vt 0.180450 0.981100
-vt 0.109542 0.971169
-vt 0.153568 0.987533
-vt 0.180450 0.981100
-vt 0.111298 0.956400
-vt 0.109542 0.971169
-vt 0.085457 0.197038
-vt 0.019460 0.184500
-vt 0.016441 0.244366
-vt 0.085457 0.197038
-vt 0.016441 0.244366
-vt 0.092094 0.249976
-vt 0.950882 0.695534
-vt 0.985549 0.630401
-vt 0.947422 0.591836
-vt 0.184488 0.927119
-vt 0.111298 0.956400
-vt 0.180450 0.981100
-vt 0.985162 0.748387
-vt 0.999011 0.739548
-vt 0.968010 0.702894
-vt 0.985162 0.748387
-vt 0.968010 0.702894
-vt 0.953135 0.699966
-vt 0.244895 0.177499
-vt 0.182349 0.154977
-vt 0.172240 0.213102
-vt 0.244895 0.177499
-vt 0.172240 0.213102
-vt 0.257933 0.241733
-vt 0.025660 0.167277
-vt 0.070813 0.179118
-vt 0.070774 0.128416
-vt 0.999011 0.739548
-vt 0.998149 0.664236
-vt 0.968010 0.702894
-vt 0.985549 0.630401
-vt 0.997250 0.630519
-vt 0.959294 0.584016
-vt 0.985549 0.630401
-vt 0.959294 0.584016
-vt 0.947422 0.591836
-vt 0.172554 0.101012
-vt 0.200499 0.139389
-vt 0.232375 0.081405
-vt 0.997250 0.630519
-vt 0.999791 0.545216
-vt 0.959294 0.584016
-vt 0.200499 0.139389
-vt 0.271388 0.170697
-vt 0.303833 0.115587
-vt 0.200499 0.139389
-vt 0.303833 0.115587
-vt 0.232375 0.081405
-vn -1.0000 0.0000 0.0000
-vn 0.0000 0.0000 -1.0000
-vn 1.0000 -0.0000 0.0000
-vn 0.0000 -0.0000 1.0000
-vn 0.0000 -1.0000 -0.0000
-vn -0.0000 1.0000 0.0000
-vn 0.0000 0.9888 0.1491
-vn -0.1491 0.0000 -0.9888
-vn 0.0000 -0.1491 0.9888
-vn 0.0000 -0.1491 -0.9888
-vn 0.9888 -0.1491 0.0000
-vn -0.9888 -0.1491 0.0000
-vn -0.1491 -0.0000 0.9888
-vn -0.1491 -0.9888 -0.0000
-vn -0.1491 0.9888 0.0000
-vn -0.9888 0.0000 0.1491
-vn 0.9888 0.0000 0.1491
-vn 0.0000 -0.9888 0.1491
-vn 0.7863 -0.6178 -0.0000
-vn -0.7854 -0.6190 -0.0000
-vn 0.7890 0.6144 0.0000
-vn -0.7857 0.6186 0.0000
-vn 0.0000 -0.6386 -0.7696
-vn 0.0000 0.6355 0.7721
-vn 0.0000 0.6400 -0.7684
-vn 0.0000 -0.6402 0.7682
-vn 0.0000 0.6337 -0.7736
-vn 0.0000 -0.6353 -0.7722
-vn 0.0000 -0.6372 0.7707
-vn 0.0000 0.6361 0.7716
-vn 0.5899 0.0000 -0.8074
-vn -0.5899 0.0000 0.8074
-s 1
-f 1/1/1 2/2/1 3/3/1
-f 4/4/1 5/5/1 6/6/1
-f 7/7/2 8/8/2 9/9/2
-f 7/7/2 9/9/2 10/10/2
-f 11/11/3 12/12/3 13/13/3
-f 11/11/3 13/13/3 14/14/3
-f 15/15/4 16/16/4 17/17/4
-f 15/15/4 17/17/4 18/18/4
-f 19/19/5 20/20/5 21/21/5
-f 19/19/5 21/21/5 22/22/5
-f 23/23/6 24/24/6 25/25/6
-f 23/23/6 25/25/6 26/26/6
-f 27/27/7 28/28/7 29/29/7
-f 30/30/7 31/31/7 32/32/7
-f 33/33/2 7/7/2 10/10/2
-f 33/33/2 10/10/2 34/34/2
-f 35/35/2 36/36/2 37/37/2
-f 35/35/2 37/37/2 38/38/2
-f 36/36/2 39/39/2 40/40/2
-f 36/36/2 40/40/2 37/37/2
-f 41/41/8 42/42/8 43/43/8
-f 44/44/8 45/45/8 46/46/8
-f 47/47/3 48/48/3 49/49/3
-f 47/47/3 49/49/3 50/50/3
-f 51/51/3 11/11/3 14/14/3
-f 51/51/3 14/14/3 52/52/3
-f 48/48/3 53/53/3 54/54/3
-f 48/48/3 54/54/3 49/49/3
-f 55/55/9 56/56/9 57/57/9
-f 58/58/9 59/59/9 60/60/9
-f 61/61/6 23/23/6 26/26/6
-f 61/61/6 26/26/6 62/62/6
-f 63/63/6 64/64/6 65/65/6
-f 63/63/6 65/65/6 66/66/6
-f 64/64/6 67/67/6 68/68/6
-f 64/64/6 68/68/6 65/65/6
-f 69/69/6 70/70/6 71/71/6
-f 69/69/6 71/71/6 72/72/6
-f 73/73/10 74/74/10 75/75/10
-f 76/76/10 77/77/10 78/78/10
-f 79/79/11 80/80/11 81/81/11
-f 82/82/11 83/83/11 84/84/11
-f 85/85/12 86/86/12 87/87/12
-f 88/88/12 89/89/12 90/90/12
-f 91/91/3 92/92/3 93/93/3
-f 91/91/3 93/93/3 94/94/3
-f 95/95/13 96/96/13 97/97/13
-f 98/98/13 99/99/13 100/100/13
-f 101/101/14 102/102/14 103/103/14
-f 104/104/14 105/105/14 106/106/14
-f 107/107/15 108/108/15 109/109/15
-f 110/110/15 111/111/15 112/112/15
-f 113/113/2 114/114/2 115/115/2
-f 116/116/2 117/117/2 118/118/2
-f 119/119/16 120/120/16 121/121/16
-f 122/122/16 123/123/16 124/124/16
-f 125/125/17 126/126/17 127/127/17
-f 128/128/17 129/129/17 130/130/17
-f 131/131/18 132/132/18 133/133/18
-f 134/134/18 135/135/18 136/136/18
-f 137/137/2 138/138/2 139/139/2
-f 140/140/2 141/141/2 142/142/2
-f 142/142/2 143/143/2 144/144/2
-f 142/142/2 144/144/2 145/145/2
-f 140/140/2 142/142/2 145/145/2
-f 146/146/2 147/147/2 148/148/2
-f 146/146/2 148/148/2 149/149/2
-f 150/150/4 151/151/4 152/152/4
-f 150/150/4 153/153/4 151/151/4
-f 154/154/4 155/155/4 156/156/4
-f 154/154/4 157/157/4 155/155/4
-f 158/158/4 157/157/4 154/154/4
-f 159/159/4 160/160/4 161/161/4
-f 162/162/4 163/163/4 164/164/4
-f 165/165/19 166/166/19 167/167/19
-f 165/165/19 167/167/19 168/168/19
-f 169/169/5 170/170/5 171/171/5
-f 169/169/5 171/171/5 172/172/5
-f 173/173/20 174/174/20 175/175/20
-f 173/173/20 175/175/20 176/176/20
-f 177/177/6 178/178/6 179/179/6
-f 180/180/6 181/181/6 182/182/6
-f 183/183/3 184/184/3 185/185/3
-f 186/186/3 187/187/3 188/188/3
-f 189/189/21 190/190/21 191/191/21
-f 189/189/21 191/191/21 192/192/21
-f 193/193/1 194/194/1 195/195/1
-f 196/196/1 197/197/1 198/198/1
-f 199/199/22 200/200/22 201/201/22
-f 202/202/22 203/203/22 204/204/22
-f 205/205/6 206/206/6 207/207/6
-f 208/208/6 209/209/6 210/210/6
-f 211/211/3 212/212/3 213/213/3
-f 211/211/3 213/213/3 214/214/3
-f 214/214/3 215/215/3 216/216/3
-f 217/217/3 218/218/3 219/219/3
-f 211/211/3 214/214/3 220/220/3
-f 221/221/3 211/211/3 220/220/3
-f 221/221/3 220/220/3 222/222/3
-f 223/223/3 219/219/3 224/224/3
-f 221/221/3 222/222/3 225/225/3
-f 226/226/3 222/222/3 227/227/3
-f 228/228/1 229/229/1 230/230/1
-f 228/228/1 231/231/1 229/229/1
-f 232/232/1 233/233/1 234/234/1
-f 231/231/1 235/235/1 236/236/1
-f 228/228/1 235/235/1 231/231/1
-f 237/237/1 235/235/1 228/228/1
-f 237/237/1 238/238/1 235/235/1
-f 238/238/1 239/239/1 235/235/1
-f 240/240/1 241/241/1 242/242/1
-f 243/243/1 244/244/1 242/242/1
-f 245/245/5 246/246/5 247/247/5
-f 245/245/5 247/247/5 248/248/5
-f 249/249/23 250/250/23 251/251/23
-f 252/252/23 253/253/23 254/254/23
-f 255/255/5 256/256/5 257/257/5
-f 255/255/5 257/257/5 258/258/5
-f 259/259/6 260/260/6 261/261/6
-f 259/259/6 261/261/6 262/262/6
-f 263/263/24 264/264/24 265/265/24
-f 266/266/24 267/267/24 268/268/24
-f 269/269/25 270/270/25 271/271/25
-f 272/272/25 273/273/25 274/274/25
-f 275/275/26 276/276/26 277/277/26
-f 278/278/26 279/279/26 280/280/26
-f 281/281/27 282/282/27 283/283/27
-f 281/281/27 283/283/27 284/284/27
-f 285/285/28 286/286/28 287/287/28
-f 285/285/28 287/287/28 288/288/28
-f 289/289/6 290/290/6 291/291/6
-f 289/289/6 291/291/6 292/292/6
-f 293/293/29 294/294/29 295/295/29
-f 293/293/29 295/295/29 296/296/29
-f 297/297/30 298/298/30 299/299/30
-f 297/297/30 299/299/30 300/300/30
-f 301/301/5 302/302/5 303/303/5
-f 304/304/5 305/305/5 306/306/5
-f 307/307/5 308/308/5 309/309/5
-f 310/310/5 311/311/5 312/312/5
-f 313/313/5 314/314/5 315/315/5
-f 316/316/5 317/317/5 318/318/5
-f 319/319/6 320/320/6 321/321/6
-f 322/322/6 323/323/6 324/324/6
-f 325/325/6 326/326/6 327/327/6
-f 328/328/6 329/329/6 330/330/6
-f 331/331/6 332/332/6 333/333/6
-f 334/334/6 335/335/6 336/336/6
-f 337/337/3 338/338/3 339/339/3
-f 340/340/3 341/341/3 342/342/3
-f 343/343/1 344/344/1 345/345/1
-f 346/346/1 347/347/1 348/348/1
-f 349/349/2 350/350/2 351/351/2
-f 352/352/2 353/353/2 354/354/2
-f 355/355/31 356/356/31 357/357/31
-f 358/358/31 359/359/31 360/360/31
-f 361/361/1 362/362/1 363/363/1
-f 364/364/1 365/365/1 366/366/1
-f 367/367/4 368/368/4 369/369/4
-f 370/370/4 371/371/4 372/372/4
-f 373/373/3 374/374/3 375/375/3
-f 376/376/3 377/377/3 378/378/3
-f 379/379/32 380/380/32 381/381/32
-f 382/382/32 383/383/32 384/384/32
diff --git a/driver/slimevr/resources/rendermodels/example_controller/example_controller.png b/driver/slimevr/resources/rendermodels/example_controller/example_controller.png
deleted file mode 100644
index 2128b089..00000000
Binary files a/driver/slimevr/resources/rendermodels/example_controller/example_controller.png and /dev/null differ
diff --git a/driver/slimevr/resources/slimevr_driver_config.json b/driver/slimevr/resources/slimevr_driver_config.json
new file mode 100644
index 00000000..4f08333d
--- /dev/null
+++ b/driver/slimevr/resources/slimevr_driver_config.json
@@ -0,0 +1,6 @@
+{
+ "external_hand_max_radius_m": 1.7,
+ "stale_external_pose_frames": 1,
+ "pose_lerp_speed": 0.8,
+ "frozen_pose_position_epsilon_m": 0.005
+}
diff --git a/libraries/openvr b/libraries/openvr
index 4c85abcb..91825305 160000
--- a/libraries/openvr
+++ b/libraries/openvr
@@ -1 +1 @@
-Subproject commit 4c85abcb7f7f1f02adaf3812018c99fc593bc341
+Subproject commit 91825305130f446f82054c1ec3d416321ace0072
diff --git a/src/IVRDevice.hpp b/src/IVRDevice.hpp
index b2681abc..6fafa0a4 100644
--- a/src/IVRDevice.hpp
+++ b/src/IVRDevice.hpp
@@ -68,6 +68,11 @@ namespace SlimeVRDriver {
* Updates device position from a received message.
*/
virtual void PositionMessage(messages::Position& position) = 0;
+
+ /**
+ * Updates device position from a received message.
+ */
+ virtual void ControllerInputMessage(messages::ControllerInput& position) = 0;
/**
* Updates device status from a received message.
diff --git a/src/IVRDriver.hpp b/src/IVRDriver.hpp
index 479f5d35..cb7bc4dc 100644
--- a/src/IVRDriver.hpp
+++ b/src/IVRDriver.hpp
@@ -1,94 +1,111 @@
#pragma once
-#include
-#include
+#include "IVRDevice.hpp"
#include
-#include
-#include
+#include
#include
-#include "IVRDevice.hpp"
+#include
#include
+#include
+#include
+
namespace SlimeVRDriver {
- class UniverseTranslation {
- public:
- // TODO: do we want to store this differently?
- vr::HmdVector3_t translation;
- float yaw;
-
- static UniverseTranslation parse(simdjson::ondemand::object &obj);
- };
-
- typedef std::variant SettingsValue;
-
- class IVRDriver : protected vr::IServerTrackedDeviceProvider {
- public:
- /**
- * Returns all devices being managed by this driver.
- *
- * @return A vector of shared pointers to all managed devices.
- */
- virtual std::vector> GetDevices() = 0;
-
- /**
- * Returns all OpenVR events that happened on the current frame.
- *
- * @return A vector of current frame's OpenVR events.
- */
- virtual std::vector GetOpenVREvents() = 0;
-
- /**
- * Returns the milliseconds between last frame and this frame.
- *
- * @return Milliseconds between last frame and this frame.
- */
- virtual std::chrono::milliseconds GetLastFrameTime() = 0;
-
- /**
- * Adds a device to the driver.
- *
- * @param device A shared pointer to the device to be added.
- * @return True on success, false on failure.
- */
- virtual bool AddDevice(std::shared_ptr device) = 0;
-
- /**
- * Returns the value of a settings key.
- *
- * @param key The settings key
- * @return Value of the key, std::monostate if the value is malformed or missing.
- */
- virtual SettingsValue GetSettingsValue(std::string key) = 0;
-
- /**
- * Gets the OpenVR VRDriverInput pointer.
- *
- * @return OpenVR VRDriverInput pointer.
- */
- virtual vr::IVRDriverInput* GetInput() = 0;
-
- /**
- * Gets the OpenVR VRDriverProperties pointer.
- *
- * @return OpenVR VRDriverProperties pointer.
- */
- virtual vr::CVRPropertyHelpers* GetProperties() = 0;
-
- /**
- * Gets the OpenVR VRServerDriverHost pointer.
- *
- * @return OpenVR VRServerDriverHost pointer.
- */
- virtual vr::IVRServerDriverHost* GetDriverHost() = 0;
-
- /**
- * Gets the current UniverseTranslation.
- */
- virtual std::optional GetCurrentUniverse() = 0;
-
- virtual inline const char* const* GetInterfaceVersions() override {
- return vr::k_InterfaceVersions;
- };
-
- virtual ~IVRDriver() {}
- };
-}
\ No newline at end of file
+class UniverseTranslation {
+public:
+ // TODO: do we want to store this differently?
+ vr::HmdVector3_t translation;
+ float yaw;
+
+ static UniverseTranslation parse(simdjson::ondemand::object &obj);
+};
+
+typedef std::variant
+ SettingsValue;
+
+class IVRDriver : protected vr::IServerTrackedDeviceProvider {
+public:
+ /**
+ * Returns all devices being managed by this driver.
+ *
+ * @return A vector of shared pointers to all managed devices.
+ */
+ virtual std::vector> GetDevices() = 0;
+
+ /**
+ * Returns all OpenVR events that happened on the current frame.
+ *
+ * @return A vector of current frame's OpenVR events.
+ */
+ virtual std::vector GetOpenVREvents() = 0;
+
+ /**
+ * Returns the milliseconds between last frame and this frame.
+ *
+ * @return Milliseconds between last frame and this frame.
+ */
+ virtual std::chrono::milliseconds GetLastFrameTime() = 0;
+
+ /**
+ * Adds a device to the driver.
+ *
+ * @param device A shared pointer to the device to be added.
+ * @return True on success, false on failure.
+ */
+ virtual bool AddDevice(std::shared_ptr device) = 0;
+
+ /**
+ * Returns the value of a settings key.
+ *
+ * @param key The settings key
+ * @return Value of the key, std::monostate if the value is malformed or
+ * missing.
+ */
+ virtual SettingsValue GetSettingsValue(std::string key) = 0;
+
+ /**
+ * Gets the OpenVR VRDriverInput pointer.
+ *
+ * @return OpenVR VRDriverInput pointer.
+ */
+ virtual vr::IVRDriverInput *GetInput() = 0;
+
+ /**
+ * Gets the OpenVR VRDriverProperties pointer.
+ *
+ * @return OpenVR VRDriverProperties pointer.
+ */
+ virtual vr::CVRPropertyHelpers *GetProperties() = 0;
+
+ /**
+ * Gets the OpenVR VRServerDriverHost pointer.
+ *
+ * @return OpenVR VRServerDriverHost pointer.
+ */
+ virtual vr::IVRServerDriverHost *GetDriverHost() = 0;
+
+ /**
+ * Gets the current UniverseTranslation.
+ */
+ virtual std::optional GetCurrentUniverse() = 0;
+
+ /**
+ * Gets the current pose from an external controller (e.g. Virtual Desktop /
+ * Steam Link on Quest) for the given hand, if one is connected and tracked.
+ * Used to prefer external hand position when in view, falling back to SlimeVR
+ * when not.
+ */
+ virtual std::optional
+ GetExternalPoseForHand(bool left_hand) = 0;
+
+ /** Pose lerp speed (0–1) for smoothing. From driver config file. */
+ virtual float GetPoseLerpSpeed() = 0;
+ /** Slower lerp speed used when swapping VD ↔ SlimeVR to smooth the transition. */
+ virtual float GetPoseLerpSpeedOnSwap() = 0;
+
+ virtual inline const char *const *GetInterfaceVersions() override {
+ return vr::k_InterfaceVersions;
+ };
+
+ virtual ~IVRDriver() {}
+};
+} // namespace SlimeVRDriver
\ No newline at end of file
diff --git a/src/TrackerDevice.cpp b/src/TrackerDevice.cpp
index 0f7852ee..547dd829 100644
--- a/src/TrackerDevice.cpp
+++ b/src/TrackerDevice.cpp
@@ -1,216 +1,727 @@
#include "TrackerDevice.hpp"
-
-SlimeVRDriver::TrackerDevice::TrackerDevice(std::string serial, int device_id, TrackerRole tracker_role):
- serial_(serial),
- tracker_role_(tracker_role),
- device_id_(device_id),
- last_pose_(MakeDefaultPose()),
- last_pose_atomic_(MakeDefaultPose())
-{ }
-
-std::string SlimeVRDriver::TrackerDevice::GetSerial() {
- return serial_;
+#include
+#include
+
+namespace fs = std::filesystem;
+
+SlimeVRDriver::TrackerDevice::TrackerDevice(std::string serial, int device_id,
+ TrackerRole tracker_role)
+ : serial_(serial), tracker_role_(tracker_role), device_id_(device_id),
+ is_left_hand_(tracker_role_ == TrackerRole::LEFT_CONTROLLER ||
+ tracker_role_ == TrackerRole::LEFT_HAND),
+ is_right_hand_(tracker_role_ == TrackerRole::RIGHT_CONTROLLER ||
+ tracker_role_ == TrackerRole::RIGHT_HAND),
+ fingertracking_enabled_(is_left_hand_ || is_right_hand_),
+ is_controller_(tracker_role_ == TrackerRole::LEFT_CONTROLLER ||
+ tracker_role_ == TrackerRole::RIGHT_CONTROLLER ||
+ tracker_role_ == TrackerRole::LEFT_HAND ||
+ tracker_role_ == TrackerRole::RIGHT_HAND),
+ last_pose_(MakeDefaultPose()), last_pose_atomic_(MakeDefaultPose()) {}
+
+std::string SlimeVRDriver::TrackerDevice::GetSerial() { return serial_; }
+
+vr::DriverPose_t
+SlimeVRDriver::TrackerDevice::LerpPose(const vr::DriverPose_t &from,
+ const vr::DriverPose_t &to, float t) {
+ vr::DriverPose_t out = to;
+ for (int i = 0; i < 3; i++)
+ out.vecPosition[i] =
+ from.vecPosition[i] + (to.vecPosition[i] - from.vecPosition[i]) * t;
+ // Nlerp rotation (pose quaternions are double)
+ double w = (1.0 - static_cast(t)) * from.qRotation.w + static_cast(t) * to.qRotation.w;
+ double x = (1.0 - static_cast(t)) * from.qRotation.x + static_cast(t) * to.qRotation.x;
+ double y = (1.0 - static_cast(t)) * from.qRotation.y + static_cast(t) * to.qRotation.y;
+ double z = (1.0 - static_cast(t)) * from.qRotation.z + static_cast(t) * to.qRotation.z;
+ double len = std::sqrt(w * w + x * x + y * y + z * z);
+ if (len > 1e-6) {
+ out.qRotation.w = w / len;
+ out.qRotation.x = x / len;
+ out.qRotation.y = y / len;
+ out.qRotation.z = z / len;
+ }
+ return out;
}
void SlimeVRDriver::TrackerDevice::Update() {
- if (device_index_ == vr::k_unTrackedDeviceIndexInvalid) return;
-
- // Check if this device was asked to be identified
- auto events = GetDriver()->GetOpenVREvents();
- for (auto event : events) {
- // Note here, event.trackedDeviceIndex does not necessarily equal device_index_, not sure why, but the component handle will match so we can just use that instead
- //if (event.trackedDeviceIndex == device_index_) {
- if (event.eventType == vr::EVREventType::VREvent_Input_HapticVibration) {
- if (event.data.hapticVibration.componentHandle == haptic_component_) {
- did_vibrate_ = true;
- }
- }
- //}
+ if (device_index_ == vr::k_unTrackedDeviceIndexInvalid)
+ return;
+
+ // Check if this device was asked to be identified
+ auto events = GetDriver()->GetOpenVREvents();
+ for (auto event : events) {
+ // Note here, event.trackedDeviceIndex does not necessarily equal device_index_, not sure why, but the component handle will match so we can just use that instead
+ // if (event.trackedDeviceIndex == device_index_) {
+ if (event.eventType == vr::EVREventType::VREvent_Input_HapticVibration) {
+ if (event.data.hapticVibration.componentHandle == haptic_component_) {
+ did_vibrate_ = true;
+ }
}
-
- // Check if we need to keep vibrating
- if (did_vibrate_) {
- vibrate_anim_state_ += GetDriver()->GetLastFrameTime().count() / 1000.f;
- if (vibrate_anim_state_ > 1.0f) {
- did_vibrate_ = false;
- vibrate_anim_state_ = 0.0f;
- }
+ //}
+ }
+
+ // Check if we need to keep vibrating
+ if (did_vibrate_) {
+ vibrate_anim_state_ += GetDriver()->GetLastFrameTime().count() / 1000.f;
+ if (vibrate_anim_state_ > 1.0f) {
+ did_vibrate_ = false;
+ vibrate_anim_state_ = 0.0f;
}
+ }
+
+ if (was_activated_ && is_controller_) {
+ // Get inputs from protobuf
+ LogInput(("Check handle for trigger before update. Value is " +
+ std::to_string(trigger_value_))
+ .c_str(),
+ this->trigger_component_);
+ vr::VRDriverInput()->UpdateScalarComponent(this->trigger_component_,
+ trigger_value_, 0);
+
+ LogInput(("Check handle for trigger touch before update. Value is " +
+ std::to_string(trigger_value_click))
+ .c_str(),
+ this->trigger_component_touch_);
+ vr::VRDriverInput()->UpdateBooleanComponent(this->trigger_component_touch_,
+ trigger_value_click, 0);
+
+ LogInput(("Check handle for grip before update. Value is " +
+ std::to_string(grip_value))
+ .c_str(),
+ this->grip_value_component_);
+ vr::VRDriverInput()->UpdateScalarComponent(this->grip_value_component_,
+ grip_value, 0);
+
+ LogInput(("Check handle for stick x before update. Value is " +
+ std::to_string(thumbstick_x_value))
+ .c_str(),
+ this->stick_x_component_);
+ vr::VRDriverInput()->UpdateScalarComponent(this->stick_x_component_,
+ thumbstick_x_value, 0);
+
+ LogInput(("Check handle for stick y before update. Value is " +
+ std::to_string(thumbstick_y_value))
+ .c_str(),
+ this->stick_y_component_);
+ vr::VRDriverInput()->UpdateScalarComponent(this->stick_y_component_,
+ thumbstick_y_value, 0);
+
+ LogInput(("Check handle for button a before update. Value is " +
+ std::to_string(button_1_value))
+ .c_str(),
+ this->button_a_component_);
+ vr::VRDriverInput()->UpdateBooleanComponent(this->button_a_component_,
+ button_1_value, 0);
+
+ LogInput(("Check handle for button b before update. Value is " +
+ std::to_string(button_2_value))
+ .c_str(),
+ this->button_b_component_);
+ vr::VRDriverInput()->UpdateBooleanComponent(this->button_b_component_,
+ button_2_value, 0);
+
+ LogInput(("Check handle for stick click before update. Value is " +
+ std::to_string(stick_click_value))
+ .c_str(),
+ this->stick_click_component_);
+ vr::VRDriverInput()->UpdateBooleanComponent(this->stick_click_component_,
+ stick_click_value, 0);
+
+ LogInput(("Check handle for system before update. Value is " +
+ std::to_string(system_click_value))
+ .c_str(),
+ this->system_component);
+ vr::VRDriverInput()->UpdateBooleanComponent(this->system_component,
+ system_click_value, 0);
+
+ LogInput(("Check handle for system touch before update. Value is " +
+ std::to_string(system_click_value))
+ .c_str(),
+ this->system_component_touch);
+ vr::VRDriverInput()->UpdateBooleanComponent(this->system_component_touch,
+ system_click_value, 0);
+ }
+
+ // Target pose: controllers use external (VD/Steam Link) when available else SlimeVR; trackers use last SlimeVR pose.
+ vr::DriverPose_t target = last_pose_atomic_.load();
+ bool have_external = false;
+ if (is_controller_) {
+ auto external = GetDriver()->GetExternalPoseForHand(is_left_hand_);
+ have_external = external.has_value();
+ if (have_external)
+ target = *external;
+ }
+ // Use slower lerp when swapping VD ↔ SlimeVR to smooth the transition.
+ float lerp_t = GetDriver()->GetPoseLerpSpeed();
+ if (is_controller_ && (have_external != last_frame_had_external_))
+ lerp_t = GetDriver()->GetPoseLerpSpeedOnSwap();
+ last_frame_had_external_ = have_external;
+ if (!smoothed_pose_.has_value())
+ smoothed_pose_ = target;
+ else
+ smoothed_pose_ = LerpPose(*smoothed_pose_, target, lerp_t);
+ GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(
+ device_index_, *smoothed_pose_, sizeof(vr::DriverPose_t));
}
-void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) {
- if (device_index_ == vr::k_unTrackedDeviceIndexInvalid) return;
-
- // Setup pose for this frame
- auto pose = last_pose_;
- //send the new position and rotation from the pipe to the tracker object
- if (position.has_x()) {
- pose.vecPosition[0] = position.x();
- pose.vecPosition[1] = position.y();
- pose.vecPosition[2] = position.z();
- }
-
- pose.qRotation.w = position.qw();
- pose.qRotation.x = position.qx();
- pose.qRotation.y = position.qy();
- pose.qRotation.z = position.qz();
-
- auto current_universe = GetDriver()->GetCurrentUniverse();
- if (current_universe.has_value()) {
- auto trans = current_universe.value();
-
- // TODO: set this once, somewhere?
- pose.vecWorldFromDriverTranslation[0] = -trans.translation.v[0];
- pose.vecWorldFromDriverTranslation[1] = -trans.translation.v[1];
- pose.vecWorldFromDriverTranslation[2] = -trans.translation.v[2];
-
- pose.qWorldFromDriverRotation.w = cos(trans.yaw / 2);
- pose.qWorldFromDriverRotation.x = 0;
- pose.qWorldFromDriverRotation.y = sin(trans.yaw / 2);
- pose.qWorldFromDriverRotation.z = 0;
+void SlimeVRDriver::TrackerDevice::PositionMessage(
+ messages::Position &position) {
+ if (device_index_ == vr::k_unTrackedDeviceIndexInvalid)
+ return;
+
+ // Setup pose for this frame
+ auto pose = last_pose_;
+ // send the new position and rotation from the pipe to the tracker object
+ if (position.has_x()) {
+ pose.vecPosition[0] = position.x();
+ pose.vecPosition[1] = position.y();
+ pose.vecPosition[2] = position.z();
+ }
+
+ pose.qRotation.w = position.qw();
+ pose.qRotation.x = position.qx();
+ pose.qRotation.y = position.qy();
+ pose.qRotation.z = position.qz();
+
+ auto current_universe = GetDriver()->GetCurrentUniverse();
+ if (current_universe.has_value()) {
+ auto trans = current_universe.value();
+
+ // TODO: set this once, somewhere?
+ pose.vecWorldFromDriverTranslation[0] = -trans.translation.v[0];
+ pose.vecWorldFromDriverTranslation[1] = -trans.translation.v[1];
+ pose.vecWorldFromDriverTranslation[2] = -trans.translation.v[2];
+
+ pose.qWorldFromDriverRotation.w = cos(trans.yaw / 2);
+ pose.qWorldFromDriverRotation.x = 0;
+ pose.qWorldFromDriverRotation.y = sin(trans.yaw / 2);
+ pose.qWorldFromDriverRotation.z = 0;
+ }
+
+ bool double_tap = false;
+ bool triple_tap = false;
+
+ // Only push SlimeVR finger data when SlimeVR is actually reporting it; when it isn't, we skip so we don't overwrite finger data from Virtual Desktop/Steam Link.
+ if (fingertracking_enabled_ && position.finger_bone_rotations_size() > 0) {
+ vr::VRBoneTransform_t finger_skeleton_[31]{};
+ for (int i = 0; i < position.finger_bone_rotations_size(); i++) {
+ auto fingerData = position.finger_bone_rotations(i);
+ int fingerBoneName = static_cast(fingerData.name());
+ int boneIndex = protobuf_fingers_to_openvr[fingerBoneName];
+ finger_skeleton_[boneIndex].orientation = {
+ fingerData.w(), fingerData.x(), fingerData.y(), fingerData.z()};
}
-
- pose.deviceIsConnected = true;
- pose.poseIsValid = true;
- pose.result = vr::ETrackingResult::TrackingResult_Running_OK;
-
- // Notify SteamVR that pose was updated
- last_pose_atomic_ = (last_pose_ = pose);
- GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(device_index_, pose, sizeof(vr::DriverPose_t));
+ // Update the finger skeleton once with the full hand. With and without
+ // controller have the same pose.
+ vr::VRDriverInput()->UpdateSkeletonComponent(
+ skeletal_component_handle_, vr::VRSkeletalMotionRange_WithController,
+ finger_skeleton_, 31);
+ vr::VRDriverInput()->UpdateSkeletonComponent(
+ skeletal_component_handle_, vr::VRSkeletalMotionRange_WithoutController,
+ finger_skeleton_, 31);
+ }
+
+ pose.deviceIsConnected = true;
+ pose.poseIsValid = true;
+ pose.result = vr::ETrackingResult::TrackingResult_Running_OK;
+
+ if (is_controller_) {
+ // Set inputs
+ vr::VRDriverInput()->UpdateBooleanComponent(this->double_tap_component_,
+ double_tap, 0);
+ vr::VRDriverInput()->UpdateBooleanComponent(this->triple_tap_component_,
+ triple_tap, 0);
+ }
+
+ // Store for Update(); pose is submitted only there.
+ last_pose_atomic_ = (last_pose_ = pose);
+}
+void SlimeVRDriver::TrackerDevice::ControllerInputMessage(
+ messages::ControllerInput &controllerInput) {
+ if (was_activated_ && is_controller_) {
+ // Get inputs from protobuf, store them for Update which is called during RunFrame
+ trigger_value_ = controllerInput.trigger();
+ trigger_value_click = controllerInput.trigger() > 0.5f;
+ grip_value = controllerInput.grip();
+ thumbstick_x_value = controllerInput.thumbstick_x();
+ thumbstick_y_value = controllerInput.thumbstick_y();
+ button_1_value = controllerInput.button_1();
+ button_2_value = controllerInput.button_2();
+ stick_click_value = controllerInput.stick_click();
+ system_value = controllerInput.menu_recenter();
+ system_click_value = controllerInput.menu_recenter();
+ }
}
-
void SlimeVRDriver::TrackerDevice::BatteryMessage(messages::Battery &battery) {
- if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid)
- return;
-
- // Get the properties handle
- auto props = GetDriver()->GetProperties()->TrackedDeviceToPropertyContainer(this->device_index_);
+ if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid)
+ return;
+
+ // Get the properties handle
+ auto containerHandle_ =
+ GetDriver()->GetProperties()->TrackedDeviceToPropertyContainer(
+ this->device_index_);
+
+ vr::ETrackedPropertyError err;
+
+ // Set that the tracker reports battery level in case it has not already been
+ // set to true It's a given that the tracker supports reporting battery life
+ // because otherwise a BatteryMessage would not be received
+ if (vr::VRProperties()->GetBoolProperty(
+ containerHandle_, vr::Prop_DeviceProvidesBatteryStatus_Bool, &err) !=
+ true) {
+ vr::VRProperties()->SetBoolProperty(
+ containerHandle_, vr::Prop_DeviceProvidesBatteryStatus_Bool, true);
+ }
+
+ if (battery.is_charging()) {
+ vr::VRProperties()->SetBoolProperty(containerHandle_,
+ vr::Prop_DeviceIsCharging_Bool, true);
+ } else {
+ vr::VRProperties()->SetBoolProperty(containerHandle_,
+ vr::Prop_DeviceIsCharging_Bool, false);
+ }
+
+ // Set the battery Level; 0 = 0%, 1 = 100%
+ vr::VRProperties()->SetFloatProperty(containerHandle_,
+ vr::Prop_DeviceBatteryPercentage_Float,
+ battery.battery_level());
+}
- vr::ETrackedPropertyError err;
+void SlimeVRDriver::TrackerDevice::StatusMessage(
+ messages::TrackerStatus &status) {
+ if (device_index_ == vr::k_unTrackedDeviceIndexInvalid)
+ return;
- // Set that the tracker reports battery level in case it has not already been set to true
- // It's a given that the tracker supports reporting battery life because otherwise a BatteryMessage would not be received
- if (vr::VRProperties()->GetBoolProperty(props, vr::Prop_DeviceProvidesBatteryStatus_Bool, &err) != true) {
- vr::VRProperties()->SetBoolProperty(props, vr::Prop_DeviceProvidesBatteryStatus_Bool, true);
- }
+ vr::DriverPose_t pose = last_pose_;
+ switch (status.status()) {
+ case messages::TrackerStatus_Status_OK:
+ pose.deviceIsConnected = true;
+ pose.poseIsValid = true;
+ break;
+ case messages::TrackerStatus_Status_DISCONNECTED:
+ pose.deviceIsConnected = false;
+ pose.poseIsValid = false;
+ break;
+ case messages::TrackerStatus_Status_ERROR:
+ case messages::TrackerStatus_Status_BUSY:
+ default:
+ pose.deviceIsConnected = true;
+ pose.poseIsValid = false;
+ break;
+ }
- if (battery.is_charging()) {
- vr::VRProperties()->SetBoolProperty(props, vr::Prop_DeviceIsCharging_Bool, true);
- } else {
- vr::VRProperties()->SetBoolProperty(props, vr::Prop_DeviceIsCharging_Bool, false);
- }
-
- // Set the battery Level; 0 = 0%, 1 = 100%
- vr::VRProperties()->SetFloatProperty(props, vr::Prop_DeviceBatteryPercentage_Float, battery.battery_level());
-}
-
-void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status) {
- if (device_index_ == vr::k_unTrackedDeviceIndexInvalid) return;
-
- vr::DriverPose_t pose = last_pose_;
- switch (status.status()) {
- case messages::TrackerStatus_Status_OK:
- pose.deviceIsConnected = true;
- pose.poseIsValid = true;
- break;
- case messages::TrackerStatus_Status_DISCONNECTED:
- pose.deviceIsConnected = false;
- pose.poseIsValid = false;
- break;
- case messages::TrackerStatus_Status_ERROR:
- case messages::TrackerStatus_Status_BUSY:
- default:
- pose.deviceIsConnected = true;
- pose.poseIsValid = false;
- break;
- }
+ // TODO: send position/rotation of 0 instead of last pose?
- // TODO: send position/rotation of 0 instead of last pose?
-
- last_pose_atomic_ = (last_pose_ = pose);
- GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(device_index_, pose, sizeof(vr::DriverPose_t));
+ // Store for Update(); pose is submitted only there.
+ last_pose_atomic_ = (last_pose_ = pose);
}
DeviceType SlimeVRDriver::TrackerDevice::GetDeviceType() {
- return DeviceType::TRACKER;
+ if (is_controller_) {
+ return DeviceType::CONTROLLER;
+ }
+ return DeviceType::TRACKER;
}
vr::TrackedDeviceIndex_t SlimeVRDriver::TrackerDevice::GetDeviceIndex() {
- return device_index_;
+ return device_index_;
}
vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) {
- device_index_ = unObjectId;
-
- logger_->Log("Activating tracker {}", serial_);
-
- // Get the properties handle
- auto props = GetDriver()->GetProperties()->TrackedDeviceToPropertyContainer(device_index_);
-
- // Set some universe ID (Must be 2 or higher)
- GetDriver()->GetProperties()->SetUint64Property(props, vr::Prop_CurrentUniverseId_Uint64, 4);
-
- // Set up a model "number" (not needed but good to have)
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_ModelNumber_String, "SlimeVR Virtual Tracker");
-
- // Opt out of hand selection
- GetDriver()->GetProperties()->SetInt32Property(props, vr::Prop_ControllerRoleHint_Int32, vr::ETrackedControllerRole::TrackedControllerRole_OptOut);
- vr::VRProperties()->SetInt32Property(props, vr::Prop_DeviceClass_Int32, vr::TrackedDeviceClass_GenericTracker);
- vr::VRProperties()->SetInt32Property(props, vr::Prop_ControllerHandSelectionPriority_Int32, -1);
-
- // Set up a render model path
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_RenderModelName_String, "{htc}/rendermodels/vr_tracker_vive_1_0");
-
- // Set the icon
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceReady_String, "{slimevr}/icons/tracker_status_ready.png");
-
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceOff_String, "{slimevr}/icons/tracker_status_off.png");
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceSearching_String, "{slimevr}/icons/tracker_status_ready.png");
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceSearchingAlert_String, "{slimevr}/icons/tracker_status_ready_alert.png");
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceReadyAlert_String, "{slimevr}/icons/tracker_status_ready_alert.png");
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceNotReady_String, "{slimevr}/icons/tracker_status_error.png");
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceStandby_String, "{slimevr}/icons/tracker_status_standby.png");
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceAlertLow_String, "{slimevr}/icons/tracker_status_ready_low.png");
-
- // Automatically select vive tracker roles and set hints for games that need it (Beat Saber avatar mod, for example)
+ device_index_ = unObjectId;
+
+ logger_->Log("Activating tracker %s", serial_.c_str());
+
+ const std::string log_dir = "C:\\Temp\\SlimeVRLogs\\";
+
+ // Create directory if it doesn't exist
+ try {
+ fs::create_directories(log_dir);
+ } catch (...) {
+ // If this fails, we silently continue (driver must not crash)
+ }
+
+ // One log file per tracker
+ const std::string log_path = log_dir + "input.log";
+
+ input_log_.open(log_path, std::ios::out | std::ios::app);
+ if (input_log_.is_open()) {
+ input_log_ << "=== Activating tracker " << serial_ << " ===" << std::endl;
+ }
+
+ // Get the properties handle
+ containerHandle_ =
+ GetDriver()->GetProperties()->TrackedDeviceToPropertyContainer(
+ device_index_);
+
+ // Set some universe ID (Must be 2 or higher)
+ GetDriver()->GetProperties()->SetUint64Property(
+ containerHandle_, vr::Prop_CurrentUniverseId_Uint64, 4);
+
+ // Set up a model "number" (not needed but good to have)
+ if (is_controller_) {
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_ModelNumber_String,
+ "SlimeVR Virtual Controller");
+ } else {
+ GetDriver()->GetProperties()->SetStringProperty(containerHandle_,
+ vr::Prop_ModelNumber_String,
+ "SlimeVR Virtual Tracker");
+ }
+
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_ManufacturerName_String, "SlimeVR");
+
+ //// Hand selection
+ if (is_left_hand_) {
+ GetDriver()->GetProperties()->SetInt32Property(
+ containerHandle_, vr::Prop_ControllerRoleHint_Int32,
+ vr::ETrackedControllerRole::TrackedControllerRole_LeftHand);
+ } else if (is_right_hand_) {
+ GetDriver()->GetProperties()->SetInt32Property(
+ containerHandle_, vr::Prop_ControllerRoleHint_Int32,
+ vr::ETrackedControllerRole::TrackedControllerRole_RightHand);
+ } else {
+ GetDriver()->GetProperties()->SetInt32Property(
+ containerHandle_, vr::Prop_ControllerRoleHint_Int32,
+ vr::ETrackedControllerRole::TrackedControllerRole_OptOut);
+ }
+
+ // Should be treated as controller or as tracker? (Hand = Tracker and
+ // Controller = Controller)
+ if (is_controller_) {
+ vr::VRProperties()->SetInt32Property(containerHandle_,
+ vr::Prop_DeviceClass_Int32,
+ vr::TrackedDeviceClass_Controller);
+ vr::VRProperties()->SetStringProperty(containerHandle_,
+ vr::Prop_ControllerType_String,
+ "slimevr_virtual_controller");
+ vr::VRProperties()->SetInt32Property(
+ containerHandle_, vr::Prop_ControllerHandSelectionPriority_Int32,
+ 2147483647); // Prioritizes our controller over whatever else.
+ } else {
+ vr::VRProperties()->SetInt32Property(containerHandle_,
+ vr::Prop_DeviceClass_Int32,
+ vr::TrackedDeviceClass_GenericTracker);
+ }
+
+ // Set up a render model path (index controllers for controllers and vive
+ // trackers 1.0 for trackers)
+ std::string model_path;
+ if (is_controller_) {
+ vr::VRProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_RenderModelName_String,
+ is_right_hand_ ? "{indexcontroller}valve_controller_knu_1_0_right"
+ : "{indexcontroller}valve_controller_knu_1_0_left");
+ } else {
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_RenderModelName_String,
+ "{htc}/rendermodels/vr_tracker_vive_1_0");
+ }
+
+ // Set the icons
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_NamedIconPathDeviceReady_String,
+ "{slimevr}/icons/tracker_status_ready.png");
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_NamedIconPathDeviceOff_String,
+ "{slimevr}/icons/tracker_status_off.png");
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_NamedIconPathDeviceSearching_String,
+ "{slimevr}/icons/tracker_status_ready.png");
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_NamedIconPathDeviceSearchingAlert_String,
+ "{slimevr}/icons/tracker_status_ready_alert.png");
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_NamedIconPathDeviceReadyAlert_String,
+ "{slimevr}/icons/tracker_status_ready_alert.png");
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_NamedIconPathDeviceNotReady_String,
+ "{slimevr}/icons/tracker_status_error.png");
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_NamedIconPathDeviceStandby_String,
+ "{slimevr}/icons/tracker_status_standby.png");
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_NamedIconPathDeviceAlertLow_String,
+ "{slimevr}/icons/tracker_status_ready_low.png");
+
+ // Set inputs
+ if (is_controller_) {
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_InputProfilePath_String,
+ "{slimevr}/input/slimevr_controller_bindings.json");
+ uint64_t supportedButtons = 0xFFFFFFFFFFFFFFFFULL;
+ vr::VRProperties()->SetUint64Property(
+ containerHandle_, vr::Prop_SupportedButtons_Uint64, supportedButtons);
+
+ LogInfo("Creating /pose/raw component");
+ vr::EVRInputError input_error = vr::VRDriverInput()->CreatePoseComponent(
+ containerHandle_, "/pose/raw", &this->raw_pose_component_handle_);
+ LogInputError(input_error, "/pose/raw", this->raw_pose_component_handle_);
+
+ LogInfo("Creating /pose/tip component");
+ input_error = vr::VRDriverInput()->CreatePoseComponent(
+ containerHandle_, "/pose/tip", &this->aim_pose_component_handle_);
+ LogInputError(input_error, "/pose/tip", this->aim_pose_component_handle_);
+
+ LogInfo("Creating /input/double_tap/click component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/double_tap/click",
+ &this->double_tap_component_);
+ LogInputError(input_error, "/input/double_tap/click",
+ this->double_tap_component_);
+
+ LogInfo("Creating /input/triple_tap/click component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/triple_tap/click",
+ &this->triple_tap_component_);
+ LogInputError(input_error, "/input/triple_tap/click",
+ this->triple_tap_component_);
+
+ LogInfo("Creating /input/a/click component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/a/click", &this->button_a_component_);
+ LogInputError(input_error, "/input/a/click", this->button_a_component_);
+
+ LogInfo("Creating /input/a/touch component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/a/touch", &this->button_a_component_touch_);
+ LogInputError(input_error, "/input/a/touch",
+ this->button_a_component_touch_);
+
+ LogInfo("Creating /input/b/click component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/b/click", &this->button_b_component_);
+ LogInputError(input_error, "/input/b/click", this->button_b_component_);
+
+ LogInfo("Creating /input/b/touch component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/b/touch", &this->button_b_component_touch_);
+ LogInputError(input_error, "/input/b/touch",
+ this->button_b_component_touch_);
+
+ LogInfo("Creating /input/system/click component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/system/click", &this->system_component);
+ LogInputError(input_error, "/input/system/click", this->system_component);
+
+ LogInfo("Creating /input/system/touch component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/system/touch", &this->system_component_touch);
+ LogInputError(input_error, "/input/system/touch",
+ this->system_component_touch);
+
+ LogInfo("Creating /input/trackpad/click component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/trackpad/click",
+ &this->trackpad_click_component_);
+ LogInputError(input_error, "/input/trackpad/click",
+ this->trackpad_click_component_);
+
+ LogInfo("Creating /input/trackpad/touch component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/trackpad/touch",
+ &this->trackpad_touch_component_);
+ LogInputError(input_error, "/input/trackpad/touch",
+ this->trackpad_touch_component_);
+
+ LogInfo("Creating /input/joystick/click component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/joystick/click",
+ &this->stick_click_component_);
+ LogInputError(input_error, "/input/joystick/click",
+ this->stick_click_component_);
+
+ LogInfo("Creating /input/thumbstick/touch component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/thumbstick/touch",
+ &this->stick_click_component_touch_);
+ LogInputError(input_error, "/input/thumbstick/touch",
+ this->stick_click_component_touch_);
+
+ // Scalar components
+
+ LogInfo("Creating /input/trigger/value component");
+ input_error = vr::VRDriverInput()->CreateScalarComponent(
+ containerHandle_, "/input/trigger/value", &this->trigger_component_,
+ vr::VRScalarType_Absolute, vr::VRScalarUnits_NormalizedOneSided);
+ LogInputError(input_error, "/input/trigger/value",
+ this->trigger_component_);
+
+ LogInfo("Creating /input/trigger/touch component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/trigger/touch",
+ &this->trigger_component_touch_);
+ LogInputError(input_error, "/input/trigger/touch",
+ this->trigger_component_touch_);
+
+ LogInfo("Creating /input/trigger/click component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/trigger/click",
+ &this->trigger_component_click_);
+ LogInputError(input_error, "/input/trigger/click",
+ this->trigger_component_click_);
+
+ LogInfo("Creating /input/grip/value component");
+ input_error = vr::VRDriverInput()->CreateScalarComponent(
+ containerHandle_, "/input/grip/value", &this->grip_value_component_,
+ vr::VRScalarType_Absolute, vr::VRScalarUnits_NormalizedOneSided);
+ LogInputError(input_error, "/input/grip/value",
+ this->grip_value_component_);
+
+ LogInfo("Creating /input/grip/touch component");
+ input_error = vr::VRDriverInput()->CreateBooleanComponent(
+ containerHandle_, "/input/grip/touch",
+ &this->grip_value_component_touch_);
+ LogInputError(input_error, "/input/grip/touch",
+ this->grip_value_component_touch_);
+
+ LogInfo("Creating /input/trackpad/x component");
+ input_error = vr::VRDriverInput()->CreateScalarComponent(
+ containerHandle_, "/input/trackpad/x", &this->trackpad_x_component_,
+ vr::VRScalarType_Absolute, vr::VRScalarUnits_NormalizedTwoSided);
+ LogInputError(input_error, "/input/trackpad/x",
+ this->trackpad_x_component_);
+
+ LogInfo("Creating /input/trackpad/y component");
+ input_error = vr::VRDriverInput()->CreateScalarComponent(
+ containerHandle_, "/input/trackpad/y", &this->trackpad_y_component_,
+ vr::VRScalarType_Absolute, vr::VRScalarUnits_NormalizedTwoSided);
+ LogInputError(input_error, "/input/trackpad/y",
+ this->trackpad_y_component_);
+
+ LogInfo("Creating /input/thumbstick/x component");
+ input_error = vr::VRDriverInput()->CreateScalarComponent(
+ containerHandle_, "/input/thumbstick/x", &this->stick_x_component_,
+ vr::VRScalarType_Absolute, vr::VRScalarUnits_NormalizedTwoSided);
+ LogInputError(input_error, "/input/thumbstick/x", this->stick_x_component_);
+
+ LogInfo("Creating /input/joystick/y component");
+ input_error = vr::VRDriverInput()->CreateScalarComponent(
+ containerHandle_, "/input/thumbstick/y", &this->stick_y_component_,
+ vr::VRScalarType_Absolute, vr::VRScalarUnits_NormalizedTwoSided);
+ LogInputError(input_error, "/input/thumbstick/y", this->stick_y_component_);
+
+ LogInfo("Creating /output/haptic component");
+ input_error = vr::VRDriverInput()->CreateHapticComponent(
+ containerHandle_, "/output/haptic", &this->haptic_component_);
+ LogInputError(input_error, "/output/haptic", this->haptic_component_);
+ }
+
+ // Automatically select vive tracker roles and set hints for games that need
+ // it (Beat Saber avatar mod, for example)
+ if (!is_controller_) {
auto role_hint = GetViveRoleHint(tracker_role_);
if (role_hint != "") {
- GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_ControllerType_String, role_hint.c_str());
+ GetDriver()->GetProperties()->SetStringProperty(
+ containerHandle_, vr::Prop_ControllerType_String, role_hint.c_str());
}
auto role = GetViveRole(tracker_role_);
if (role != "") {
- vr::VRSettings()->SetString(vr::k_pch_Trackers_Section, ("/devices/slimevr/" + serial_).c_str(), role.c_str());
+ vr::VRSettings()->SetString(vr::k_pch_Trackers_Section,
+ ("/devices/slimevr/" + serial_).c_str(),
+ role.c_str());
}
+ }
+
+ // Setup skeletal input for fingertracking
+ if (fingertracking_enabled_) {
+ vr::VRDriverInput()->CreateSkeletonComponent(
+ containerHandle_,
+ is_right_hand_ ? "/input/skeleton/right" : "/input/skeleton/left",
+ is_right_hand_ ? "/skeleton/hand/right" : "/skeleton/hand/left",
+ "/pose/raw", vr::EVRSkeletalTrackingLevel::VRSkeletalTracking_Full,
+ NULL, // Fist
+ 31, &skeletal_component_handle_);
+
+ // Update the skeleton so steamvr knows we have an active skeletal input
+ // device
+ vr::VRBoneTransform_t finger_skeleton_[31]{};
+ vr::VRDriverInput()->UpdateSkeletonComponent(
+ skeletal_component_handle_, vr::VRSkeletalMotionRange_WithController,
+ finger_skeleton_, 31);
+ vr::VRDriverInput()->UpdateSkeletonComponent(
+ skeletal_component_handle_, vr::VRSkeletalMotionRange_WithoutController,
+ finger_skeleton_, 31);
+ }
+ was_activated_ = true;
+ return vr::EVRInitError::VRInitError_None;
+}
- return vr::EVRInitError::VRInitError_None;
+void SlimeVRDriver::TrackerDevice::LogInfo(const char *message) {
+ if (input_log_.is_open()) {
+ input_log_ << "[Info] " << message << std::endl;
+ input_log_.flush();
+ }
}
-void SlimeVRDriver::TrackerDevice::Deactivate() {
- device_index_ = vr::k_unTrackedDeviceIndexInvalid;
+void SlimeVRDriver::TrackerDevice::LogInputError(
+ vr::EVRInputError err, const char *path,
+ vr::VRInputComponentHandle_t componentHandle) {
+ if (!input_log_.is_open())
+ return;
+
+ bool validHandle = componentHandle != vr::k_ulInvalidInputComponentHandle;
+ input_log_ << "[" << (err == vr::VRInputError_None ? "Info" : "InputError")
+ << "] " << path << "\r\n Handle: " << componentHandle
+ << "\r\n Handle Is Valid: " << (validHandle ? "true" : "false")
+ << "\r\n Failure Result: " << GetInputErrorName(err) << " (" << err
+ << ")" << std::endl;
+ input_log_.flush(); // force write immediately
+}
+void SlimeVRDriver::TrackerDevice::LogInput(
+ const char *path, vr::VRInputComponentHandle_t componentHandle) {
+ if (!input_log_.is_open())
+ return;
+
+ bool validHandle = componentHandle != vr::k_ulInvalidInputComponentHandle;
+ input_log_ << "["
+ << "Info"
+ << "] " << path << "\r\n Handle: " << componentHandle
+ << "\r\n Handle Is Valid: " << (validHandle ? "true" : "false")
+ << std::endl;
+ input_log_.flush(); // force write immediately
}
-void SlimeVRDriver::TrackerDevice::EnterStandby() {
+const char *
+SlimeVRDriver::TrackerDevice::GetInputErrorName(vr::EVRInputError err) {
+ switch (err) {
+ case vr::VRInputError_None:
+ return "None";
+ case vr::VRInputError_NameNotFound:
+ return "NameNotFound";
+ case vr::VRInputError_WrongType:
+ return "WrongType";
+ // Add others as needed
+ default:
+ return "Unknown";
+ }
}
+void SlimeVRDriver::TrackerDevice::Deactivate() {
+ device_index_ = vr::k_unTrackedDeviceIndexInvalid;
+}
+
+void SlimeVRDriver::TrackerDevice::EnterStandby() {}
-void* SlimeVRDriver::TrackerDevice::GetComponent(const char* pchComponentNameAndVersion) {
- return nullptr;
+void *SlimeVRDriver::TrackerDevice::GetComponent(
+ const char *pchComponentNameAndVersion) {
+ return nullptr;
}
-void SlimeVRDriver::TrackerDevice::DebugRequest(const char* pchRequest, char* pchResponseBuffer, uint32_t unResponseBufferSize) {
- if (unResponseBufferSize >= 1) {
- pchResponseBuffer[0] = 0;
- }
+void SlimeVRDriver::TrackerDevice::DebugRequest(const char *pchRequest,
+ char *pchResponseBuffer,
+ uint32_t unResponseBufferSize) {
+ if (unResponseBufferSize >= 1) {
+ pchResponseBuffer[0] = 0;
+ }
}
vr::DriverPose_t SlimeVRDriver::TrackerDevice::GetPose() {
- return last_pose_atomic_;
+ return last_pose_atomic_;
}
-int SlimeVRDriver::TrackerDevice::GetDeviceId() {
- return device_id_;
-}
+int SlimeVRDriver::TrackerDevice::GetDeviceId() { return device_id_; }
void SlimeVRDriver::TrackerDevice::SetDeviceId(int device_id) {
- device_id_ = device_id;
+ device_id_ = device_id;
}
diff --git a/src/TrackerDevice.hpp b/src/TrackerDevice.hpp
index 76536553..7b4d2ab1 100644
--- a/src/TrackerDevice.hpp
+++ b/src/TrackerDevice.hpp
@@ -1,63 +1,150 @@
#pragma once
+#include
#include
#include
-#include
+#include
#include
-#include
#include
+#include
-#include
-#include
+#include
+#include
#include
+#include
#include
-#include "TrackerRole.hpp"
+#include
+
#include "Logger.hpp"
+#include "TrackerRole.hpp"
namespace SlimeVRDriver {
- class TrackerDevice : public IVRDevice {
- public:
- TrackerDevice(std::string serial, int device_id, TrackerRole tracker_role);
- ~TrackerDevice() = default;
-
- // Inherited via IVRDevice
- virtual std::string GetSerial() override;
- virtual void Update() override;
- virtual vr::TrackedDeviceIndex_t GetDeviceIndex() override;
- virtual DeviceType GetDeviceType() override;
- virtual int GetDeviceId() override;
- virtual void SetDeviceId(int device_id) override;
- virtual void PositionMessage(messages::Position &position) override;
- virtual void StatusMessage(messages::TrackerStatus &status) override;
- virtual void BatteryMessage(messages::Battery &battery) override;
-
- // Inherited via ITrackedDeviceServerDriver
- virtual vr::EVRInitError Activate(uint32_t unObjectId) override;
- virtual void Deactivate() override;
- virtual void EnterStandby() override;
- virtual void* GetComponent(const char* pchComponentNameAndVersion) override;
- virtual void DebugRequest(const char* pchRequest, char* pchResponseBuffer, uint32_t unResponseBufferSize) override;
- virtual vr::DriverPose_t GetPose() override;
-
- private:
- std::shared_ptr logger_ = std::make_shared();
-
- std::atomic device_index_ = vr::k_unTrackedDeviceIndexInvalid;
- std::string serial_;
-
- int device_id_;
- TrackerRole tracker_role_;
-
- vr::DriverPose_t last_pose_ = IVRDevice::MakeDefaultPose();
- std::atomic last_pose_atomic_ = IVRDevice::MakeDefaultPose();
-
- bool did_vibrate_ = false;
- float vibrate_anim_state_ = 0.f;
-
- vr::VRInputComponentHandle_t haptic_component_ = 0;
- vr::VRInputComponentHandle_t system_click_component_ = 0;
- vr::VRInputComponentHandle_t system_touch_component_ = 0;
- };
-};
\ No newline at end of file
+class TrackerDevice : public IVRDevice {
+public:
+ TrackerDevice(std::string serial, int device_id, TrackerRole tracker_role);
+ ~TrackerDevice() = default;
+ void LogInput(const char *path, vr::VRInputComponentHandle_t componentHandle);
+ void LogInputError(vr::EVRInputError err, const char *path,
+ vr::VRInputComponentHandle_t componentHandle);
+ void LogInfo(const char *message);
+ // Inherited via IVRDevice
+ virtual std::string GetSerial() override;
+ virtual void Update() override;
+ virtual vr::TrackedDeviceIndex_t GetDeviceIndex() override;
+ virtual DeviceType GetDeviceType() override;
+ virtual int GetDeviceId() override;
+ virtual void SetDeviceId(int device_id) override;
+ virtual void PositionMessage(messages::Position &position) override;
+ virtual void
+ ControllerInputMessage(messages::ControllerInput &position) override;
+ virtual void StatusMessage(messages::TrackerStatus &status) override;
+ virtual void BatteryMessage(messages::Battery &battery) override;
+ const char *GetInputErrorName(vr::EVRInputError err);
+
+ // Inherited via ITrackedDeviceServerDriver
+ virtual vr::EVRInitError Activate(uint32_t unObjectId) override;
+ virtual void Deactivate() override;
+ virtual void EnterStandby() override;
+ virtual void *GetComponent(const char *pchComponentNameAndVersion) override;
+ virtual void DebugRequest(const char *pchRequest, char *pchResponseBuffer,
+ uint32_t unResponseBufferSize) override;
+ virtual vr::DriverPose_t GetPose() override;
+ vr::HmdMatrix34_t ToHmdMatrix(const vr::DriverPose_t &pose);
+
+private:
+ std::ofstream input_log_;
+ std::shared_ptr logger_ = std::make_shared();
+
+ std::atomic device_index_ =
+ vr::k_unTrackedDeviceIndexInvalid;
+ std::string serial_;
+ vr::PropertyContainerHandle_t containerHandle_;
+
+ int device_id_;
+ TrackerRole tracker_role_;
+ bool fingertracking_enabled_;
+
+ vr::DriverPose_t last_pose_ = IVRDevice::MakeDefaultPose();
+ std::atomic last_pose_atomic_ =
+ IVRDevice::MakeDefaultPose();
+ std::optional smoothed_pose_;
+ bool last_frame_had_external_ = false;
+ static vr::DriverPose_t LerpPose(const vr::DriverPose_t &from,
+ const vr::DriverPose_t &to, float t);
+
+ bool did_vibrate_ = false;
+ float vibrate_anim_state_ = 0.f;
+ bool was_activated_ = false;
+ vr::VRInputComponentHandle_t haptic_component_ = 0;
+ vr::VRInputComponentHandle_t double_tap_component_ = 0;
+ vr::VRInputComponentHandle_t triple_tap_component_ = 0;
+
+ vr::VRInputComponentHandle_t ignored = 0;
+ vr::VRInputComponentHandle_t pose_component_handle_ = 0;
+
+ vr::VRInputComponentHandle_t raw_pose_component_handle_ = 0;
+ vr::VRInputComponentHandle_t aim_pose_component_handle_ = 0;
+
+ vr::VRInputComponentHandle_t trigger_component_ = 0;
+ vr::VRInputComponentHandle_t grip_value_component_ = 0;
+ vr::VRInputComponentHandle_t stick_x_component_ = 0;
+ vr::VRInputComponentHandle_t stick_y_component_ = 0;
+ vr::VRInputComponentHandle_t button_a_component_ = 0;
+ vr::VRInputComponentHandle_t button_b_component_ = 0;
+
+ vr::VRInputComponentHandle_t trackpad_x_component_ = 0;
+ vr::VRInputComponentHandle_t trackpad_y_component_ = 0;
+ vr::VRInputComponentHandle_t trackpad_click_component_ = 0;
+ vr::VRInputComponentHandle_t trackpad_touch_component_ = 0;
+
+ vr::VRInputComponentHandle_t stick_click_component_ = 0;
+ vr::VRInputComponentHandle_t system_component = 0;
+ vr::VRInputComponentHandle_t system_component_touch = 0;
+
+ vr::VRInputComponentHandle_t trigger_component_click_ = 0;
+ vr::VRInputComponentHandle_t trigger_component_touch_ = 0;
+
+ vr::VRInputComponentHandle_t grip_value_component_touch_ = 0;
+ vr::VRInputComponentHandle_t stick_x_component_touch_ = 0;
+ vr::VRInputComponentHandle_t stick_y_component_touch_ = 0;
+ vr::VRInputComponentHandle_t button_a_component_touch_ = 0;
+ vr::VRInputComponentHandle_t button_b_component_touch_ = 0;
+ vr::VRInputComponentHandle_t stick_click_component_touch_ = 0;
+
+ bool is_controller_;
+ bool is_left_hand_;
+ bool is_right_hand_;
+
+ vr::VRInputComponentHandle_t skeletal_component_handle_;
+
+ float trigger_value_ = 0;
+ bool trigger_value_click = false;
+ float grip_value = 0;
+ float thumbstick_x_value = 0;
+ float thumbstick_y_value = 0;
+ bool button_1_value = false;
+ bool button_2_value = false;
+ bool stick_click_value = false;
+ bool system_value = false;
+ bool system_click_value = false;
+ const int protobuf_fingers_to_openvr[15] = {
+ 2, // THUMB_METACARPAL → eBone_Thumb1
+ 3, // THUMB_PROXIMAL → eBone_Thumb2
+ 4, // THUMB_DISTAL → eBone_Thumb3
+ 6, // INDEX_PROXIMAL → eBone_IndexFinger1
+ 7, // INDEX_INTERMEDIATE → eBone_IndexFinger2
+ 8, // INDEX_DISTAL → eBone_IndexFinger3
+ 11, // MIDDLE_PROXIMAL → eBone_MiddleFinger1
+ 12, // MIDDLE_INTERMEDIATE → eBone_MiddleFinger2
+ 13, // MIDDLE_DISTAL → eBone_MiddleFinger3
+ 16, // RING_PROXIMAL → eBone_RingFinger1
+ 17, // RING_INTERMEDIATE → eBone_RingFinger2
+ 18, // RING_DISTAL → eBone_RingFinger3
+ 21, // LITTLE_PROXIMAL → eBone_PinkyFinger1
+ 22, // LITTLE_INTERMEDIATE → eBone_PinkyFinger2
+ 23 // LITTLE_DISTAL → eBone_PinkyFinger3
+ };
+};
+}; // namespace SlimeVRDriver
\ No newline at end of file
diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp
index c249d13f..c9a379f1 100644
--- a/src/VRDriver.cpp
+++ b/src/VRDriver.cpp
@@ -1,439 +1,700 @@
#include "VRDriver.hpp"
-#include
#include "TrackerRole.hpp"
+#include "VRPaths_openvr.hpp"
+#include
+#include
+#include
#include
#include
-#include "VRPaths_openvr.hpp"
-
-vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverContext) {
- // Perform driver context initialisation
- if (vr::EVRInitError init_error = vr::InitServerDriverContext(pDriverContext); init_error != vr::EVRInitError::VRInitError_None) {
- return init_error;
- }
-
- logger_->Log("Activating SlimeVR Driver...");
-
- try {
- auto json = simdjson::padded_string::load(GetVRPathRegistryFilename()); // load VR Path Registry
- simdjson::ondemand::document doc = json_parser_.iterate(json);
- auto path = std::string { doc.get_object()["config"].at(0).get_string().value() };
- default_chap_path_ = GetDefaultChaperoneFromConfigPath(path);
- } catch (simdjson::simdjson_error& e) {
- logger_->Log("Error getting VR Config path, continuing (error code {})", std::to_string(e.error()));
- }
-
- logger_->Log("SlimeVR Driver Loaded Successfully");
-
- bridge_ = std::make_shared(
- std::static_pointer_cast(std::make_shared("Bridge")),
- std::bind(&SlimeVRDriver::VRDriver::OnBridgeMessage, this, std::placeholders::_1)
- );
- bridge_->Start();
-
- exiting_pose_request_thread_ = false;
- pose_request_thread_ =
- std::make_unique(&SlimeVRDriver::VRDriver::RunPoseRequestThread, this);
-
- return vr::VRInitError_None;
+#include
+
+vr::EVRInitError
+SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext *pDriverContext) {
+ // Perform driver context initialisation
+ if (vr::EVRInitError init_error = vr::InitServerDriverContext(pDriverContext);
+ init_error != vr::EVRInitError::VRInitError_None) {
+ return init_error;
+ }
+
+ logger_->Log("Activating SlimeVR Driver...");
+
+ try {
+ auto json = simdjson::padded_string::load(
+ GetVRPathRegistryFilename()); // load VR Path Registry
+ simdjson::ondemand::document doc = json_parser_.iterate(json);
+ auto path =
+ std::string{doc.get_object()["config"].at(0).get_string().value()};
+ default_chap_path_ = GetDefaultChaperoneFromConfigPath(path);
+ } catch (simdjson::simdjson_error &e) {
+ logger_->Log("Error getting VR Config path, continuing (error code {})",
+ std::to_string(e.error()));
+ }
+
+ logger_->Log("SlimeVR Driver Loaded Successfully");
+
+ LoadDriverConfig();
+
+ bridge_ = std::make_shared(
+ std::static_pointer_cast(std::make_shared("Bridge")),
+ std::bind(&SlimeVRDriver::VRDriver::OnBridgeMessage, this,
+ std::placeholders::_1));
+ bridge_->Start();
+
+ exiting_pose_request_thread_ = false;
+ pose_request_thread_ = std::make_unique(
+ &SlimeVRDriver::VRDriver::RunPoseRequestThread, this);
+
+ return vr::VRInitError_None;
}
void SlimeVRDriver::VRDriver::Cleanup() {
- exiting_pose_request_thread_ = true;
- pose_request_thread_->join();
- pose_request_thread_.reset();
- bridge_->Stop();
+ exiting_pose_request_thread_ = true;
+ pose_request_thread_->join();
+ pose_request_thread_.reset();
+ bridge_->Stop();
}
void SlimeVRDriver::VRDriver::RunPoseRequestThread() {
- logger_->Log("Pose request thread started");
- while (!exiting_pose_request_thread_) {
- if (!bridge_->IsConnected()) {
- // If bridge not connected, assume we need to resend hmd tracker add message
- sent_hmd_add_message_ = false;
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- continue;
- }
+ logger_->Log("Pose request thread started");
+ while (!exiting_pose_request_thread_) {
+ if (!bridge_->IsConnected()) {
+ // If bridge not connected, assume we need to resend hmd tracker add message
+ sent_hmd_add_message_ = false;
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ continue;
+ }
- messages::ProtobufMessage* message = google::protobuf::Arena::CreateMessage(&arena_);
-
- if (!sent_hmd_add_message_) {
- // Send add message for HMD
- messages::TrackerAdded* tracker_added = google::protobuf::Arena::CreateMessage(&arena_);
- message->set_allocated_tracker_added(tracker_added);
- tracker_added->set_tracker_id(0);
- tracker_added->set_tracker_role(TrackerRole::HMD);
- tracker_added->set_tracker_serial("HMD");
- tracker_added->set_tracker_name("HMD");
- bridge_->SendBridgeMessage(*message);
-
- messages::TrackerStatus* tracker_status = google::protobuf::Arena::CreateMessage(&arena_);
- message->set_allocated_tracker_status(tracker_status);
- tracker_status->set_tracker_id(0);
- tracker_status->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK);
- bridge_->SendBridgeMessage(*message);
-
- sent_hmd_add_message_ = true;
- logger_->Log("Sent HMD hello message");
- }
+ messages::ProtobufMessage *message =
+ google::protobuf::Arena::CreateMessage(
+ &arena_);
+
+ if (!sent_hmd_add_message_) {
+ // Send add message for HMD
+ messages::TrackerAdded *tracker_added =
+ google::protobuf::Arena::CreateMessage(
+ &arena_);
+ message->set_allocated_tracker_added(tracker_added);
+ tracker_added->set_tracker_id(0);
+ tracker_added->set_tracker_role(TrackerRole::HMD);
+ tracker_added->set_tracker_serial("HMD");
+ tracker_added->set_tracker_name("HMD");
+ bridge_->SendBridgeMessage(*message);
+
+ messages::TrackerStatus *tracker_status =
+ google::protobuf::Arena::CreateMessage(
+ &arena_);
+ message->set_allocated_tracker_status(tracker_status);
+ tracker_status->set_tracker_id(0);
+ tracker_status->set_status(
+ messages::TrackerStatus_Status::TrackerStatus_Status_OK);
+ bridge_->SendBridgeMessage(*message);
+
+ sent_hmd_add_message_ = true;
+ logger_->Log("Sent HMD hello message");
+ }
- vr::PropertyContainerHandle_t hmd_prop_container =
- vr::VRProperties()->TrackedDeviceToPropertyContainer(vr::k_unTrackedDeviceIndex_Hmd);
-
- vr::ETrackedPropertyError universe_error;
- uint64_t universe = vr::VRProperties()->GetUint64Property(hmd_prop_container, vr::Prop_CurrentUniverseId_Uint64, &universe_error);
- if (universe_error == vr::ETrackedPropertyError::TrackedProp_Success) {
- if (!current_universe_.has_value() || current_universe_.value().first != universe) {
- auto result = SearchUniverses(universe);
- if (result.has_value()) {
- current_universe_.emplace(universe, result.value());
- logger_->Log("Found current universe");
- } else {
- logger_->Log("Failed to find current universe!");
- }
- }
- } else if (universe_error != last_universe_error_) {
- logger_->Log("Failed to find current universe: Prop_CurrentUniverseId_Uint64 error = {}",
- vr::VRPropertiesRaw()->GetPropErrorNameFromEnum(universe_error)
- );
- }
- last_universe_error_ = universe_error;
-
- vr::TrackedDevicePose_t hmd_pose;
- vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0.0f, &hmd_pose, 1);
-
- vr::HmdQuaternion_t q = GetRotation(hmd_pose.mDeviceToAbsoluteTracking);
- vr::HmdVector3_t pos = GetPosition(hmd_pose.mDeviceToAbsoluteTracking);
-
- if (current_universe_.has_value()) {
- auto trans = current_universe_.value().second;
- pos.v[0] += trans.translation.v[0];
- pos.v[1] += trans.translation.v[1];
- pos.v[2] += trans.translation.v[2];
-
- // rotate by quaternion w = cos(-trans.yaw / 2), x = 0, y = sin(-trans.yaw / 2), z = 0
- auto tmp_w = cos(-trans.yaw / 2);
- auto tmp_y = sin(-trans.yaw / 2);
- auto new_w = tmp_w * q.w - tmp_y * q.y;
- auto new_x = tmp_w * q.x + tmp_y * q.z;
- auto new_y = tmp_w * q.y + tmp_y * q.w;
- auto new_z = tmp_w * q.z - tmp_y * q.x;
-
- q.w = new_w;
- q.x = new_x;
- q.y = new_y;
- q.z = new_z;
-
- // rotate point on the xz plane by -trans.yaw radians
- // this is equivilant to the quaternion multiplication, after applying the double angle formula.
- float tmp_sin = sin(-trans.yaw);
- float tmp_cos = cos(-trans.yaw);
- auto pos_x = pos.v[0] * tmp_cos + pos.v[2] * tmp_sin;
- auto pos_z = pos.v[0] * -tmp_sin + pos.v[2] * tmp_cos;
-
- pos.v[0] = pos_x;
- pos.v[2] = pos_z;
+ vr::PropertyContainerHandle_t hmd_prop_container =
+ vr::VRProperties()->TrackedDeviceToPropertyContainer(
+ vr::k_unTrackedDeviceIndex_Hmd);
+
+ vr::ETrackedPropertyError universe_error;
+ uint64_t universe = vr::VRProperties()->GetUint64Property(
+ hmd_prop_container, vr::Prop_CurrentUniverseId_Uint64, &universe_error);
+ if (universe_error == vr::ETrackedPropertyError::TrackedProp_Success) {
+ if (!current_universe_.has_value() ||
+ current_universe_.value().first != universe) {
+ auto result = SearchUniverses(universe);
+ if (result.has_value()) {
+ current_universe_.emplace(universe, result.value());
+ logger_->Log("Found current universe");
+ } else {
+ logger_->Log("Failed to find current universe!");
}
+ }
+ } else if (universe_error != last_universe_error_) {
+ logger_->Log(
+ "Failed to find current universe: Prop_CurrentUniverseId_Uint64 "
+ "error = {}",
+ vr::VRPropertiesRaw()->GetPropErrorNameFromEnum(universe_error));
+ }
+ last_universe_error_ = universe_error;
- messages::Position* hmd_position = google::protobuf::Arena::CreateMessage(&arena_);
- message->set_allocated_position(hmd_position);
- hmd_position->set_tracker_id(0);
- hmd_position->set_data_source(messages::Position_DataSource_FULL);
- hmd_position->set_x(pos.v[0]);
- hmd_position->set_y(pos.v[1]);
- hmd_position->set_z(pos.v[2]);
- hmd_position->set_qx((float) q.x);
- hmd_position->set_qy((float) q.y);
- hmd_position->set_qz((float) q.z);
- hmd_position->set_qw((float) q.w);
- bridge_->SendBridgeMessage(*message);
+ vr::TrackedDevicePose_t hmd_pose;
+ vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0.0f, &hmd_pose, 1);
- auto now = std::chrono::steady_clock::now();
- if (std::chrono::duration_cast(now - battery_sent_at_).count() > 100) {
- vr::ETrackedPropertyError err;
- if (vr::VRProperties()->GetBoolProperty(hmd_prop_container, vr::Prop_DeviceProvidesBatteryStatus_Bool, &err)) {
- messages::Battery* hmdBattery = google::protobuf::Arena::CreateMessage(&arena_);
- message->set_allocated_battery(hmdBattery);
- hmdBattery->set_tracker_id(0);
- hmdBattery->set_battery_level(vr::VRProperties()->GetFloatProperty(hmd_prop_container, vr::Prop_DeviceBatteryPercentage_Float, &err) * 100);
- hmdBattery->set_is_charging(vr::VRProperties()->GetBoolProperty(hmd_prop_container, vr::Prop_DeviceIsCharging_Bool, &err));
- bridge_->SendBridgeMessage(*message);
- }
- battery_sent_at_ = now;
- }
+ vr::HmdQuaternion_t q = GetRotation(hmd_pose.mDeviceToAbsoluteTracking);
+ vr::HmdVector3_t pos = GetPosition(hmd_pose.mDeviceToAbsoluteTracking);
- arena_.Reset();
-
- std::this_thread::sleep_for(std::chrono::milliseconds(2));
+ if (current_universe_.has_value()) {
+ auto trans = current_universe_.value().second;
+ pos.v[0] += trans.translation.v[0];
+ pos.v[1] += trans.translation.v[1];
+ pos.v[2] += trans.translation.v[2];
+
+ // rotate by quaternion w = cos(-trans.yaw/2), x = 0, y = sin(-trans.yaw/2), z = 0
+ auto tmp_w = cos(-trans.yaw / 2);
+ auto tmp_y = sin(-trans.yaw / 2);
+ auto new_w = tmp_w * q.w - tmp_y * q.y;
+ auto new_x = tmp_w * q.x + tmp_y * q.z;
+ auto new_y = tmp_w * q.y + tmp_y * q.w;
+ auto new_z = tmp_w * q.z - tmp_y * q.x;
+
+ q.w = new_w;
+ q.x = new_x;
+ q.y = new_y;
+ q.z = new_z;
+
+ // rotate point on the xz plane by -trans.yaw radians; equivalent to the quaternion multiplication after applying the double angle formula.
+ float tmp_sin = sin(-trans.yaw);
+ float tmp_cos = cos(-trans.yaw);
+ auto pos_x = pos.v[0] * tmp_cos + pos.v[2] * tmp_sin;
+ auto pos_z = pos.v[0] * -tmp_sin + pos.v[2] * tmp_cos;
+
+ pos.v[0] = pos_x;
+ pos.v[2] = pos_z;
}
- logger_->Log("Pose request thread exited");
-}
-void SlimeVRDriver::VRDriver::RunFrame() {
- // Collect events
- vr::VREvent_t event;
- std::vector events;
- while (vr::VRServerDriverHost()->PollNextEvent(&event, sizeof(event))) {
- events.push_back(event);
- }
- openvr_events_ = std::move(events);
-
- // Update frame timing
- std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
- frame_timing_ = std::chrono::duration_cast(now - last_frame_time_);
- last_frame_time_ = now;
-
- // Update devices
- {
- std::lock_guard lock(devices_mutex_);
- for (auto& device : devices_) {
- device->Update();
- }
+ messages::Position *hmd_position =
+ google::protobuf::Arena::CreateMessage(&arena_);
+ message->set_allocated_position(hmd_position);
+ hmd_position->set_tracker_id(0);
+ hmd_position->set_data_source(messages::Position_DataSource_FULL);
+ hmd_position->set_x(pos.v[0]);
+ hmd_position->set_y(pos.v[1]);
+ hmd_position->set_z(pos.v[2]);
+ hmd_position->set_qx((float)q.x);
+ hmd_position->set_qy((float)q.y);
+ hmd_position->set_qz((float)q.z);
+ hmd_position->set_qw((float)q.w);
+ bridge_->SendBridgeMessage(*message);
+
+ auto now = std::chrono::steady_clock::now();
+ if (std::chrono::duration_cast(now -
+ battery_sent_at_)
+ .count() > 100) {
+ vr::ETrackedPropertyError err;
+ if (vr::VRProperties()->GetBoolProperty(
+ hmd_prop_container, vr::Prop_DeviceProvidesBatteryStatus_Bool,
+ &err)) {
+ messages::Battery *hmdBattery =
+ google::protobuf::Arena::CreateMessage(&arena_);
+ message->set_allocated_battery(hmdBattery);
+ hmdBattery->set_tracker_id(0);
+ hmdBattery->set_battery_level(
+ vr::VRProperties()->GetFloatProperty(
+ hmd_prop_container, vr::Prop_DeviceBatteryPercentage_Float,
+ &err) *
+ 100);
+ hmdBattery->set_is_charging(vr::VRProperties()->GetBoolProperty(
+ hmd_prop_container, vr::Prop_DeviceIsCharging_Bool, &err));
+ bridge_->SendBridgeMessage(*message);
+ }
+ battery_sent_at_ = now;
}
+
+ arena_.Reset();
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(2));
+ }
+ logger_->Log("Pose request thread exited");
}
-void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& message) {
+void SlimeVRDriver::VRDriver::RunFrame() {
+ // Collect events
+ vr::VREvent_t event;
+ std::vector events;
+ while (vr::VRServerDriverHost()->PollNextEvent(&event, sizeof(event))) {
+ events.push_back(event);
+ }
+ openvr_events_ = std::move(events);
+
+ // Update frame timing
+ std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
+ frame_timing_ = std::chrono::duration_cast(
+ now - last_frame_time_);
+ last_frame_time_ = now;
+
+ // Update external controller poses (e.g. Virtual Desktop / Steam Link on Quest) so we can prefer their hand position when in view and fall back to SlimeVR when not.
+ {
std::lock_guard lock(devices_mutex_);
- if (message.has_tracker_added()) {
- messages::TrackerAdded ta = message.tracker_added();
- switch(GetDeviceType(static_cast(ta.tracker_role()))) {
- case DeviceType::TRACKER:
- AddDevice(std::make_shared(ta.tracker_serial(), ta.tracker_id(), static_cast(ta.tracker_role())));
- break;
- }
- } else if (message.has_position()) {
- messages::Position pos = message.position();
- auto device = devices_by_id_.find(pos.tracker_id());
- if (device != devices_by_id_.end()) {
- device->second->PositionMessage(pos);
- }
- } else if (message.has_tracker_status()) {
- messages::TrackerStatus status = message.tracker_status();
- auto device = devices_by_id_.find(status.tracker_id());
- if (device != devices_by_id_.end()) {
- device->second->StatusMessage(status);
- static const std::unordered_map status_map = {
- { messages::TrackerStatus_Status_OK, "OK" },
- { messages::TrackerStatus_Status_DISCONNECTED, "DISCONNECTED" },
- { messages::TrackerStatus_Status_ERROR, "ERROR" },
- { messages::TrackerStatus_Status_BUSY, "BUSY" },
- };
- if (status_map.count(status.status())) {
- logger_->Log("Tracker status id {} status {}", status.tracker_id(), status_map.at(status.status()));
- }
- }
- } else if (message.has_battery()) {
- messages::Battery bat = message.battery();
- auto device = this->devices_by_id_.find(bat.tracker_id());
- if (device != this->devices_by_id_.end()) {
- device->second->BatteryMessage(bat);
- }
+ UpdateExternalControllerPoses();
+ }
+
+ // Update devices
+ {
+ std::lock_guard lock(devices_mutex_);
+ for (auto &device : devices_) {
+ device->Update();
}
+ }
}
-bool SlimeVRDriver::VRDriver::ShouldBlockStandbyMode() {
- return false;
+void SlimeVRDriver::VRDriver::OnBridgeMessage(
+ const messages::ProtobufMessage &message) {
+ std::lock_guard lock(devices_mutex_);
+ if (message.has_tracker_added()) {
+ messages::TrackerAdded ta = message.tracker_added();
+ AddDevice(std::make_shared(
+ ta.tracker_serial(), ta.tracker_id(),
+ static_cast(ta.tracker_role())));
+ } else if (message.has_position()) {
+ messages::Position pos = message.position();
+ auto device = devices_by_id_.find(pos.tracker_id());
+ if (device != devices_by_id_.end()) {
+ device->second->PositionMessage(pos);
+ }
+ } else if (message.has_controller_input()) {
+ messages::ControllerInput controllerInput = message.controller_input();
+ auto device = devices_by_id_.find(controllerInput.tracker_id());
+ if (device != devices_by_id_.end()) {
+ device->second->ControllerInputMessage(controllerInput);
+ }
+ } else if (message.has_tracker_status()) {
+ messages::TrackerStatus status = message.tracker_status();
+ auto device = devices_by_id_.find(status.tracker_id());
+ if (device != devices_by_id_.end()) {
+ device->second->StatusMessage(status);
+ static const std::unordered_map
+ status_map = {
+ {messages::TrackerStatus_Status_OK, "OK"},
+ {messages::TrackerStatus_Status_DISCONNECTED, "DISCONNECTED"},
+ {messages::TrackerStatus_Status_ERROR, "ERROR"},
+ {messages::TrackerStatus_Status_BUSY, "BUSY"},
+ };
+ if (status_map.count(status.status())) {
+ logger_->Log("Tracker status id {} status {}", status.tracker_id(),
+ status_map.at(status.status()));
+ }
+ }
+ } else if (message.has_battery()) {
+ messages::Battery bat = message.battery();
+ auto device = this->devices_by_id_.find(bat.tracker_id());
+ if (device != this->devices_by_id_.end()) {
+ device->second->BatteryMessage(bat);
+ }
+ }
}
-void SlimeVRDriver::VRDriver::EnterStandby() {
-}
+bool SlimeVRDriver::VRDriver::ShouldBlockStandbyMode() { return false; }
-void SlimeVRDriver::VRDriver::LeaveStandby() {
-}
+void SlimeVRDriver::VRDriver::EnterStandby() {}
-std::vector> SlimeVRDriver::VRDriver::GetDevices() {
- std::lock_guard lock(devices_mutex_);
- std::vector> devices;
- devices.assign(devices.begin(), devices.end());
- return devices;
+void SlimeVRDriver::VRDriver::LeaveStandby() {}
+
+std::vector>
+SlimeVRDriver::VRDriver::GetDevices() {
+ std::lock_guard lock(devices_mutex_);
+ std::vector> devices;
+ devices.assign(devices.begin(), devices.end());
+ return devices;
}
std::vector SlimeVRDriver::VRDriver::GetOpenVREvents() {
- return openvr_events_;
+ return openvr_events_;
}
std::chrono::milliseconds SlimeVRDriver::VRDriver::GetLastFrameTime() {
- return frame_timing_;
+ return frame_timing_;
}
bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) {
- vr::ETrackedDeviceClass openvr_device_class;
- // Remember to update this switch when new device types are added
- switch (device->GetDeviceType()) {
- case DeviceType::CONTROLLER:
- openvr_device_class = vr::ETrackedDeviceClass::TrackedDeviceClass_Controller;
- break;
- case DeviceType::HMD:
- openvr_device_class = vr::ETrackedDeviceClass::TrackedDeviceClass_HMD;
- break;
- case DeviceType::TRACKER:
- openvr_device_class = vr::ETrackedDeviceClass::TrackedDeviceClass_GenericTracker;
- break;
- case DeviceType::TRACKING_REFERENCE:
- openvr_device_class = vr::ETrackedDeviceClass::TrackedDeviceClass_TrackingReference;
- break;
- default:
- return false;
+ vr::ETrackedDeviceClass openvr_device_class;
+ // Remember to update this switch when new device types are added
+ switch (device->GetDeviceType()) {
+ case DeviceType::CONTROLLER:
+ openvr_device_class =
+ vr::ETrackedDeviceClass::TrackedDeviceClass_Controller;
+ break;
+ case DeviceType::HMD:
+ openvr_device_class = vr::ETrackedDeviceClass::TrackedDeviceClass_HMD;
+ break;
+ case DeviceType::TRACKER:
+ openvr_device_class =
+ vr::ETrackedDeviceClass::TrackedDeviceClass_GenericTracker;
+ break;
+ case DeviceType::TRACKING_REFERENCE:
+ openvr_device_class =
+ vr::ETrackedDeviceClass::TrackedDeviceClass_TrackingReference;
+ break;
+ default:
+ return false;
+ }
+ if (!devices_by_serial_.count(device->GetSerial())) {
+ bool result = vr::VRServerDriverHost()->TrackedDeviceAdded(
+ device->GetSerial().c_str(), openvr_device_class, device.get());
+ if (result) {
+ devices_.push_back(device);
+ devices_by_id_[device->GetDeviceId()] = device;
+ devices_by_serial_[device->GetSerial()] = device;
+ logger_->Log("New tracker device added {} (id {})", device->GetSerial(),
+ device->GetDeviceId());
+ } else {
+ logger_->Log("Failed to add tracker device {} (id {})",
+ device->GetSerial(), device->GetDeviceId());
+ return false;
}
- if (!devices_by_serial_.count(device->GetSerial())) {
- bool result = vr::VRServerDriverHost()->TrackedDeviceAdded(device->GetSerial().c_str(), openvr_device_class, device.get());
- if (result) {
- devices_.push_back(device);
- devices_by_id_[device->GetDeviceId()] = device;
- devices_by_serial_[device->GetSerial()] = device;
- logger_->Log("New tracker device added {} (id {})", device->GetSerial(), device->GetDeviceId());
- } else {
- logger_->Log("Failed to add tracker device {} (id {})", device->GetSerial(), device->GetDeviceId());
- return false;
- }
+ } else {
+ std::shared_ptr oldDevice =
+ devices_by_serial_[device->GetSerial()];
+ if (oldDevice->GetDeviceId() != device->GetDeviceId()) {
+ devices_by_id_[device->GetDeviceId()] = oldDevice;
+ oldDevice->SetDeviceId(device->GetDeviceId());
+ logger_->Log("Device overridden from id {} to {} for serial {}",
+ oldDevice->GetDeviceId(), device->GetDeviceId(),
+ device->GetSerial());
} else {
- std::shared_ptr oldDevice = devices_by_serial_[device->GetSerial()];
- if (oldDevice->GetDeviceId() != device->GetDeviceId()) {
- devices_by_id_[device->GetDeviceId()] = oldDevice;
- oldDevice->SetDeviceId(device->GetDeviceId());
- logger_->Log("Device overridden from id {} to {} for serial {}", oldDevice->GetDeviceId(), device->GetDeviceId(), device->GetSerial());
- } else {
- logger_->Log("Device readded id {}, serial {}", device->GetDeviceId(), device->GetSerial());
- }
+ logger_->Log("Device readded id {}, serial {}", device->GetDeviceId(),
+ device->GetSerial());
}
- return true;
+ }
+ return true;
}
-SlimeVRDriver::SettingsValue SlimeVRDriver::VRDriver::GetSettingsValue(std::string key) {
- vr::EVRSettingsError err = vr::EVRSettingsError::VRSettingsError_None;
- int int_value = vr::VRSettings()->GetInt32(settings_key_.c_str(), key.c_str(), &err);
- if (err == vr::EVRSettingsError::VRSettingsError_None) {
- return int_value;
- }
- err = vr::EVRSettingsError::VRSettingsError_None;
- float float_value = vr::VRSettings()->GetFloat(settings_key_.c_str(), key.c_str(), &err);
- if (err == vr::EVRSettingsError::VRSettingsError_None) {
- return float_value;
- }
- err = vr::EVRSettingsError::VRSettingsError_None;
- bool bool_value = vr::VRSettings()->GetBool(settings_key_.c_str(), key.c_str(), &err);
- if (err == vr::EVRSettingsError::VRSettingsError_None) {
- return bool_value;
- }
- std::string str_value;
- str_value.reserve(1024);
- vr::VRSettings()->GetString(settings_key_.c_str(), key.c_str(), str_value.data(), 1024, &err);
- if (err == vr::EVRSettingsError::VRSettingsError_None) {
- return str_value;
- }
- err = vr::EVRSettingsError::VRSettingsError_None;
+void SlimeVRDriver::VRDriver::LoadDriverConfig() {
+ vr::IVRResources *res = vr::VRResources();
+ if (!res) return;
+ char path[1024];
+ uint32_t len = res->GetResourceFullPath(
+ "slimevr_driver_config.json", "", path, sizeof(path));
+ if (len == 0 || len >= sizeof(path)) return;
+ path[len] = '\0';
+ try {
+ auto json = simdjson::padded_string::load(path);
+ simdjson::ondemand::document doc = json_parser_.iterate(json);
+ auto obj = doc.get_object();
+ if (auto v = obj["external_hand_max_radius_m"]; !v.error()) config_external_hand_max_radius_m_ = static_cast(v.get_double());
+ if (auto v = obj["stale_external_pose_frames"]; !v.error()) config_stale_external_pose_frames_ = static_cast(v.get_int64());
+ if (auto v = obj["pose_lerp_speed"]; !v.error()) config_pose_lerp_speed_ = static_cast(v.get_double());
+ if (auto v = obj["pose_lerp_speed_on_swap"]; !v.error()) config_pose_lerp_speed_on_swap_ = static_cast(v.get_double());
+ if (auto v = obj["frozen_pose_position_epsilon_m"]; !v.error()) config_frozen_pose_position_epsilon_m_ = static_cast(v.get_double());
+ logger_->Log("Loaded driver config from {}", path);
+ } catch (const simdjson::simdjson_error &) {
+ // Use defaults; config file missing or invalid
+ }
+}
+
+float SlimeVRDriver::VRDriver::GetPoseLerpSpeed() {
+ return config_pose_lerp_speed_;
+}
- return SettingsValue();
+float SlimeVRDriver::VRDriver::GetPoseLerpSpeedOnSwap() {
+ return config_pose_lerp_speed_on_swap_;
}
-vr::IVRDriverInput* SlimeVRDriver::VRDriver::GetInput() {
- return vr::VRDriverInput();
+SlimeVRDriver::SettingsValue
+SlimeVRDriver::VRDriver::GetSettingsValue(std::string key) {
+ vr::EVRSettingsError err = vr::EVRSettingsError::VRSettingsError_None;
+ int int_value =
+ vr::VRSettings()->GetInt32(settings_key_.c_str(), key.c_str(), &err);
+ if (err == vr::EVRSettingsError::VRSettingsError_None) {
+ return int_value;
+ }
+ err = vr::EVRSettingsError::VRSettingsError_None;
+ float float_value =
+ vr::VRSettings()->GetFloat(settings_key_.c_str(), key.c_str(), &err);
+ if (err == vr::EVRSettingsError::VRSettingsError_None) {
+ return float_value;
+ }
+ err = vr::EVRSettingsError::VRSettingsError_None;
+ bool bool_value =
+ vr::VRSettings()->GetBool(settings_key_.c_str(), key.c_str(), &err);
+ if (err == vr::EVRSettingsError::VRSettingsError_None) {
+ return bool_value;
+ }
+ std::string str_value;
+ str_value.reserve(1024);
+ vr::VRSettings()->GetString(settings_key_.c_str(), key.c_str(),
+ str_value.data(), 1024, &err);
+ if (err == vr::EVRSettingsError::VRSettingsError_None) {
+ return str_value;
+ }
+ err = vr::EVRSettingsError::VRSettingsError_None;
+
+ return SettingsValue();
}
-vr::CVRPropertyHelpers* SlimeVRDriver::VRDriver::GetProperties() {
- return vr::VRProperties();
+vr::IVRDriverInput *SlimeVRDriver::VRDriver::GetInput() {
+ return vr::VRDriverInput();
}
-vr::IVRServerDriverHost* SlimeVRDriver::VRDriver::GetDriverHost() {
- return vr::VRServerDriverHost();
+vr::CVRPropertyHelpers *SlimeVRDriver::VRDriver::GetProperties() {
+ return vr::VRProperties();
+}
+
+vr::IVRServerDriverHost *SlimeVRDriver::VRDriver::GetDriverHost() {
+ return vr::VRServerDriverHost();
}
//-----------------------------------------------------------------------------
-// Purpose: Calculates quaternion (qw,qx,qy,qz) representing the rotation
-// from: https://github.com/Omnifinity/OpenVR-Tracking-Example/blob/master/HTC%20Lighthouse%20Tracking%20Example/LighthouseTracking.cpp
+// Purpose: Calculates quaternion (qw,qx,qy,qz) representing the rotation. From: https://github.com/Omnifinity/OpenVR-Tracking-Example/blob/master/HTC%20Lighthouse%20Tracking%20Example/LighthouseTracking.cpp
//-----------------------------------------------------------------------------
-vr::HmdQuaternion_t SlimeVRDriver::VRDriver::GetRotation(vr::HmdMatrix34_t &matrix) {
- vr::HmdQuaternion_t q;
-
- q.w = sqrt(fmax(0, 1 + matrix.m[0][0] + matrix.m[1][1] + matrix.m[2][2])) / 2;
- q.x = sqrt(fmax(0, 1 + matrix.m[0][0] - matrix.m[1][1] - matrix.m[2][2])) / 2;
- q.y = sqrt(fmax(0, 1 - matrix.m[0][0] + matrix.m[1][1] - matrix.m[2][2])) / 2;
- q.z = sqrt(fmax(0, 1 - matrix.m[0][0] - matrix.m[1][1] + matrix.m[2][2])) / 2;
- q.x = copysign(q.x, matrix.m[2][1] - matrix.m[1][2]);
- q.y = copysign(q.y, matrix.m[0][2] - matrix.m[2][0]);
- q.z = copysign(q.z, matrix.m[1][0] - matrix.m[0][1]);
- return q;
+vr::HmdQuaternion_t
+SlimeVRDriver::VRDriver::GetRotation(vr::HmdMatrix34_t &matrix) {
+ vr::HmdQuaternion_t q;
+
+ q.w = sqrt(fmax(0, 1 + matrix.m[0][0] + matrix.m[1][1] + matrix.m[2][2])) / 2;
+ q.x = sqrt(fmax(0, 1 + matrix.m[0][0] - matrix.m[1][1] - matrix.m[2][2])) / 2;
+ q.y = sqrt(fmax(0, 1 - matrix.m[0][0] + matrix.m[1][1] - matrix.m[2][2])) / 2;
+ q.z = sqrt(fmax(0, 1 - matrix.m[0][0] - matrix.m[1][1] + matrix.m[2][2])) / 2;
+ q.x = copysign(q.x, matrix.m[2][1] - matrix.m[1][2]);
+ q.y = copysign(q.y, matrix.m[0][2] - matrix.m[2][0]);
+ q.z = copysign(q.z, matrix.m[1][0] - matrix.m[0][1]);
+ return q;
}
//-----------------------------------------------------------------------------
// Purpose: Extracts position (x,y,z).
-// from: https://github.com/Omnifinity/OpenVR-Tracking-Example/blob/master/HTC%20Lighthouse%20Tracking%20Example/LighthouseTracking.cpp
+// from:
+// https://github.com/Omnifinity/OpenVR-Tracking-Example/blob/master/HTC%20Lighthouse%20Tracking%20Example/LighthouseTracking.cpp
//-----------------------------------------------------------------------------
-vr::HmdVector3_t SlimeVRDriver::VRDriver::GetPosition(vr::HmdMatrix34_t &matrix) {
- vr::HmdVector3_t vector;
+vr::HmdVector3_t
+SlimeVRDriver::VRDriver::GetPosition(vr::HmdMatrix34_t &matrix) {
+ vr::HmdVector3_t vector;
- vector.v[0] = matrix.m[0][3];
- vector.v[1] = matrix.m[1][3];
- vector.v[2] = matrix.m[2][3];
+ vector.v[0] = matrix.m[0][3];
+ vector.v[1] = matrix.m[1][3];
+ vector.v[2] = matrix.m[2][3];
- return vector;
+ return vector;
}
-SlimeVRDriver::UniverseTranslation SlimeVRDriver::UniverseTranslation::parse(simdjson::ondemand::object &obj) {
- SlimeVRDriver::UniverseTranslation res;
- int iii = 0;
- for (auto component: obj["translation"]) {
- if (iii > 2) {
- break; // TODO: 4 components in a translation vector? should this be an error?
- }
- res.translation.v[iii] = static_cast(component.get_double());
- iii += 1;
+SlimeVRDriver::UniverseTranslation
+SlimeVRDriver::UniverseTranslation::parse(simdjson::ondemand::object &obj) {
+ SlimeVRDriver::UniverseTranslation res;
+ int iii = 0;
+ for (auto component : obj["translation"]) {
+ if (iii > 2) {
+ break; // TODO: 4 components in a translation vector? should this be an error?
}
- res.yaw = static_cast(obj["yaw"].get_double());
+ res.translation.v[iii] = static_cast(component.get_double());
+ iii += 1;
+ }
+ res.yaw = static_cast(obj["yaw"].get_double());
- return res;
+ return res;
}
-std::optional SlimeVRDriver::VRDriver::SearchUniverse(std::string path, uint64_t target) {
- try {
- auto json = simdjson::padded_string::load(path); // load VR Path Registry
- simdjson::ondemand::document doc = json_parser_.iterate(json);
-
- for (simdjson::ondemand::object uni: doc["universes"]) {
- // TODO: universeID comes after the translation, would it be faster to unconditionally parse the translation?
- auto elem = uni["universeID"];
- uint64_t parsed_universe;
-
- auto is_integer = elem.is_integer();
- if (!is_integer.error() && is_integer.value_unsafe()) {
- parsed_universe = elem.get_uint64();
- } else {
- parsed_universe = elem.get_uint64_in_string();
- }
-
- if (parsed_universe == target) {
- auto standing_uni = uni["standing"].get_object();
- return SlimeVRDriver::UniverseTranslation::parse(standing_uni.value());
- }
- }
- } catch (simdjson::simdjson_error& e) {
- logger_->Log("Error getting universes from {}: {}", path, e.what());
- return std::nullopt;
+std::optional
+SlimeVRDriver::VRDriver::SearchUniverse(std::string path, uint64_t target) {
+ try {
+ auto json = simdjson::padded_string::load(path); // load VR Path Registry
+ simdjson::ondemand::document doc = json_parser_.iterate(json);
+
+ for (simdjson::ondemand::object uni : doc["universes"]) {
+ // TODO: universeID comes after the translation, would it be faster to unconditionally parse the translation?
+ auto elem = uni["universeID"];
+ uint64_t parsed_universe;
+
+ auto is_integer = elem.is_integer();
+ if (!is_integer.error() && is_integer.value_unsafe()) {
+ parsed_universe = elem.get_uint64();
+ } else {
+ parsed_universe = elem.get_uint64_in_string();
+ }
+
+ if (parsed_universe == target) {
+ auto standing_uni = uni["standing"].get_object();
+ return SlimeVRDriver::UniverseTranslation::parse(standing_uni.value());
+ }
}
-
+ } catch (simdjson::simdjson_error &e) {
+ logger_->Log("Error getting universes from {}: {}", path, e.what());
return std::nullopt;
+ }
+
+ return std::nullopt;
}
-std::optional SlimeVRDriver::VRDriver::SearchUniverses(uint64_t target) {
- auto driver_chap_path = vr::VRProperties()->GetStringProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DriverProvidedChaperonePath_String);
- if (driver_chap_path != "") {
- auto driver_res = SearchUniverse(driver_chap_path, target);
- if (driver_res.has_value()) {
- return driver_res.value();
- }
+std::optional
+SlimeVRDriver::VRDriver::SearchUniverses(uint64_t target) {
+ auto driver_chap_path = vr::VRProperties()->GetStringProperty(
+ vr::VRProperties()->TrackedDeviceToPropertyContainer(0),
+ vr::Prop_DriverProvidedChaperonePath_String);
+ if (driver_chap_path != "") {
+ auto driver_res = SearchUniverse(driver_chap_path, target);
+ if (driver_res.has_value()) {
+ return driver_res.value();
}
+ }
- if (default_chap_path_.has_value()) {
- return SearchUniverse(default_chap_path_.value(), target);
- }
-
- return std::nullopt;
+ if (default_chap_path_.has_value()) {
+ return SearchUniverse(default_chap_path_.value(), target);
+ }
+
+ return std::nullopt;
}
-std::optional SlimeVRDriver::VRDriver::GetCurrentUniverse() {
- if (current_universe_.has_value()) {
- return current_universe_.value().second;
+std::optional
+SlimeVRDriver::VRDriver::GetCurrentUniverse() {
+ if (current_universe_.has_value()) {
+ return current_universe_.value().second;
+ }
+
+ return std::nullopt;
+}
+
+std::optional
+SlimeVRDriver::VRDriver::GetExternalPoseForHand(bool left_hand) {
+ if (left_hand && external_left_pose_.has_value()) {
+ return external_left_pose_;
+ }
+ if (!left_hand && external_right_pose_.has_value()) {
+ return external_right_pose_;
+ }
+ return std::nullopt;
+}
+
+vr::DriverPose_t SlimeVRDriver::VRDriver::DriverPoseFromTrackedDevicePose(
+ const vr::TrackedDevicePose_t &raw) {
+ vr::DriverPose_t pose =
+ IVRDevice::MakeDefaultPose(raw.bDeviceIsConnected, raw.bPoseIsValid);
+ pose.poseTimeOffset = 0.0;
+ pose.result = raw.eTrackingResult;
+
+ vr::HmdMatrix34_t m = raw.mDeviceToAbsoluteTracking;
+ pose.qRotation = GetRotation(m);
+ vr::HmdVector3_t pos = GetPosition(m);
+ pose.vecPosition[0] = pos.v[0];
+ pose.vecPosition[1] = pos.v[1];
+ pose.vecPosition[2] = pos.v[2];
+
+ pose.vecVelocity[0] = raw.vVelocity.v[0];
+ pose.vecVelocity[1] = raw.vVelocity.v[1];
+ pose.vecVelocity[2] = raw.vVelocity.v[2];
+ pose.vecAngularVelocity[0] = raw.vAngularVelocity.v[0];
+ pose.vecAngularVelocity[1] = raw.vAngularVelocity.v[1];
+ pose.vecAngularVelocity[2] = raw.vAngularVelocity.v[2];
+
+ // External poses are already in tracking space; use identity
+ // world-from-driver
+ pose.qWorldFromDriverRotation.w = 1.0;
+ pose.qWorldFromDriverRotation.x = 0.0;
+ pose.qWorldFromDriverRotation.y = 0.0;
+ pose.qWorldFromDriverRotation.z = 0.0;
+ pose.vecWorldFromDriverTranslation[0] = 0.0;
+ pose.vecWorldFromDriverTranslation[1] = 0.0;
+ pose.vecWorldFromDriverTranslation[2] = 0.0;
+ pose.qDriverFromHeadRotation.w = 1.0;
+ pose.qDriverFromHeadRotation.x = 0.0;
+ pose.qDriverFromHeadRotation.y = 0.0;
+ pose.qDriverFromHeadRotation.z = 0.0;
+ pose.vecDriverFromHeadTranslation[0] = 0.0;
+ pose.vecDriverFromHeadTranslation[1] = 0.0;
+ pose.vecDriverFromHeadTranslation[2] = 0.0;
+
+ return pose;
+}
+
+bool SlimeVRDriver::VRDriver::ExternalPoseEquals(const vr::DriverPose_t &a,
+ const vr::DriverPose_t &b) const {
+ float pos_eps = config_frozen_pose_position_epsilon_m_;
+ for (int i = 0; i < 3; i++) {
+ if (std::fabs(a.vecPosition[i] - b.vecPosition[i]) > pos_eps)
+ return false;
+ }
+ return true;
+}
+
+bool SlimeVRDriver::VRDriver::ExternalHandInFrontAndInRadius(
+ const double hand_pos[3], const vr::TrackedDevicePose_t &hmd_pose) const {
+ if (!hmd_pose.bPoseIsValid)
+ return false;
+ double r = static_cast(config_external_hand_max_radius_m_);
+ const auto &m = hmd_pose.mDeviceToAbsoluteTracking.m;
+ double hx = m[0][3], hy = m[1][3], hz = m[2][3];
+ double dx = hand_pos[0] - hx, dy = hand_pos[1] - hy, dz = hand_pos[2] - hz;
+ double dist_sq = dx * dx + dy * dy + dz * dz;
+ if (dist_sq > r * r)
+ return false;
+ // Forward = -Z in OpenVR (third column of rotation)
+ double fx = -m[0][2], fy = -m[1][2], fz = -m[2][2];
+ if (dx * fx + dy * fy + dz * fz < 0.0)
+ return false; // behind HMD
+ return true;
+}
+
+void SlimeVRDriver::VRDriver::UpdateExternalControllerPoses() {
+ vr::TrackedDevicePose_t raw_poses[vr::k_unMaxTrackedDeviceCount];
+ vr::VRServerDriverHost()->GetRawTrackedDevicePoses(
+ 0.0f, raw_poses, vr::k_unMaxTrackedDeviceCount);
+
+ std::unordered_set our_indices;
+ for (const auto &device : devices_) {
+ vr::TrackedDeviceIndex_t idx = device->GetDeviceIndex();
+ if (idx != vr::k_unTrackedDeviceIndexInvalid) {
+ our_indices.insert(idx);
+ }
+ }
+
+ const vr::TrackedDevicePose_t &hmd_pose = raw_poses[0];
+ auto *props = GetProperties();
+ for (uint32_t i = 1; i < vr::k_unMaxTrackedDeviceCount; i++) {
+ if (our_indices.count(i))
+ continue;
+ const vr::TrackedDevicePose_t &p = raw_poses[i];
+
+ vr::PropertyContainerHandle_t container =
+ props->TrackedDeviceToPropertyContainer(i);
+ vr::ETrackedPropertyError err =
+ vr::ETrackedPropertyError::TrackedProp_Success;
+ int32_t device_class =
+ props->GetInt32Property(container, vr::Prop_DeviceClass_Int32, &err);
+ if (err != vr::ETrackedPropertyError::TrackedProp_Success ||
+ device_class != vr::TrackedDeviceClass_Controller) {
+ continue;
+ }
+ int32_t role = props->GetInt32Property(
+ container, vr::Prop_ControllerRoleHint_Int32, &err);
+ if (err != vr::ETrackedPropertyError::TrackedProp_Success)
+ continue;
+
+ // Never use our own driver's controllers as "external".
+ char manufacturer[256] = {};
+ props->GetStringProperty(container, vr::Prop_ManufacturerName_String,
+ manufacturer, sizeof(manufacturer), &err);
+ if (err == vr::ETrackedPropertyError::TrackedProp_Success &&
+ std::strcmp(manufacturer, "SlimeVR") == 0) {
+ continue;
}
- return std::nullopt;
+ vr::DriverPose_t driver_pose = DriverPoseFromTrackedDevicePose(p);
+ // Only use external hand when it's in front of HMD and within 170 cm radius; else SlimeVR.
+ if (!ExternalHandInFrontAndInRadius(driver_pose.vecPosition, hmd_pose)) {
+ if (role == vr::TrackedControllerRole_LeftHand)
+ external_left_pose_ = std::nullopt;
+ else if (role == vr::TrackedControllerRole_RightHand)
+ external_right_pose_ = std::nullopt;
+ // Don't update last_external_* so next frame we don't compare against a pose we rejected
+ continue;
+ }
+ if (role == vr::TrackedControllerRole_LeftHand) {
+ if (last_external_left_pose_.has_value() &&
+ ExternalPoseEquals(driver_pose, *last_external_left_pose_)) {
+ stale_external_left_frames_++;
+ if (stale_external_left_frames_ >= config_stale_external_pose_frames_)
+ external_left_pose_ = std::nullopt; // swap to SlimeVR
+ else
+ external_left_pose_ = driver_pose;
+ } else {
+ stale_external_left_frames_ = 0;
+ external_left_pose_ = driver_pose;
+ }
+ last_external_left_pose_ = driver_pose;
+ } else if (role == vr::TrackedControllerRole_RightHand) {
+ if (last_external_right_pose_.has_value() &&
+ ExternalPoseEquals(driver_pose, *last_external_right_pose_)) {
+ stale_external_right_frames_++;
+ if (stale_external_right_frames_ >= config_stale_external_pose_frames_)
+ external_right_pose_ = std::nullopt; // swap to SlimeVR
+ else
+ external_right_pose_ = driver_pose;
+ } else {
+ stale_external_right_frames_ = 0;
+ external_right_pose_ = driver_pose;
+ }
+ last_external_right_pose_ = driver_pose;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp
index 0dbe26a1..c2b000b2 100644
--- a/src/VRDriver.hpp
+++ b/src/VRDriver.hpp
@@ -1,77 +1,107 @@
#pragma once
#define NOMINMAX
-#include
#include
#include
+#include
#include
-#include
#include
+#include
#include
-#include "bridge/BridgeClient.hpp"
#include "Logger.hpp"
+#include "bridge/BridgeClient.hpp"
namespace SlimeVRDriver {
- class VRDriver : public IVRDriver {
- public:
- // Inherited via IVRDriver
- virtual std::vector> GetDevices() override;
- virtual std::vector GetOpenVREvents() override;
- virtual std::chrono::milliseconds GetLastFrameTime() override;
- virtual bool AddDevice(std::shared_ptr device) override;
- virtual SettingsValue GetSettingsValue(std::string key) override;
-
- virtual vr::IVRDriverInput* GetInput() override;
- virtual vr::CVRPropertyHelpers* GetProperties() override;
- virtual vr::IVRServerDriverHost* GetDriverHost() override;
-
- // Inherited via IServerTrackedDeviceProvider
- virtual vr::EVRInitError Init(vr::IVRDriverContext* pDriverContext) override;
- virtual void Cleanup() override;
- virtual void RunFrame() override;
- virtual bool ShouldBlockStandbyMode() override;
- virtual void EnterStandby() override;
- virtual void LeaveStandby() override;
- virtual ~VRDriver() = default;
-
- virtual std::optional GetCurrentUniverse() override;
-
- void OnBridgeMessage(const messages::ProtobufMessage& message);
- void RunPoseRequestThread();
-
- private:
- std::unique_ptr pose_request_thread_ = nullptr;
- std::atomic exiting_pose_request_thread_ = false;
-
- std::shared_ptr bridge_ = nullptr;
- google::protobuf::Arena arena_;
- std::shared_ptr logger_ = std::make_shared();
- std::mutex devices_mutex_;
- std::vector> devices_;
- std::vector openvr_events_;
- std::map> devices_by_id_;
- std::map> devices_by_serial_;
- std::chrono::milliseconds frame_timing_ = std::chrono::milliseconds(16);
- std::chrono::steady_clock::time_point last_frame_time_ = std::chrono::steady_clock::now();
- std::chrono::steady_clock::time_point battery_sent_at_ = std::chrono::steady_clock::now();
- std::string settings_key_ = "driver_slimevr";
-
- vr::HmdQuaternion_t GetRotation(vr::HmdMatrix34_t &matrix);
- vr::HmdVector3_t GetPosition(vr::HmdMatrix34_t &matrix);
-
- bool sent_hmd_add_message_ = false;
-
- simdjson::ondemand::parser json_parser_;
- std::optional default_chap_path_ = std::nullopt;
- //std::map universes;
-
- vr::ETrackedPropertyError last_universe_error_;
- std::optional> current_universe_ = std::nullopt;
- std::optional SearchUniverse(std::string path, uint64_t target);
- std::optional SearchUniverses(uint64_t target);
- };
-};
\ No newline at end of file
+class VRDriver : public IVRDriver {
+public:
+ // Inherited via IVRDriver
+ virtual std::vector> GetDevices() override;
+ virtual std::vector GetOpenVREvents() override;
+ virtual std::chrono::milliseconds GetLastFrameTime() override;
+ virtual bool AddDevice(std::shared_ptr device) override;
+ virtual SettingsValue GetSettingsValue(std::string key) override;
+
+ virtual vr::IVRDriverInput *GetInput() override;
+ virtual vr::CVRPropertyHelpers *GetProperties() override;
+ virtual vr::IVRServerDriverHost *GetDriverHost() override;
+
+ // Inherited via IServerTrackedDeviceProvider
+ virtual vr::EVRInitError Init(vr::IVRDriverContext *pDriverContext) override;
+ virtual void Cleanup() override;
+ virtual void RunFrame() override;
+ virtual bool ShouldBlockStandbyMode() override;
+ virtual void EnterStandby() override;
+ virtual void LeaveStandby() override;
+ virtual ~VRDriver() = default;
+
+ virtual std::optional GetCurrentUniverse() override;
+
+ virtual std::optional
+ GetExternalPoseForHand(bool left_hand) override;
+
+ void OnBridgeMessage(const messages::ProtobufMessage &message);
+ void RunPoseRequestThread();
+
+private:
+ std::unique_ptr pose_request_thread_ = nullptr;
+ std::atomic exiting_pose_request_thread_ = false;
+
+ std::shared_ptr bridge_ = nullptr;
+ google::protobuf::Arena arena_;
+ std::shared_ptr logger_ = std::make_shared();
+ std::mutex devices_mutex_;
+ std::vector> devices_;
+ std::vector openvr_events_;
+ std::map> devices_by_id_;
+ std::map> devices_by_serial_;
+ std::chrono::milliseconds frame_timing_ = std::chrono::milliseconds(16);
+ std::chrono::steady_clock::time_point last_frame_time_ =
+ std::chrono::steady_clock::now();
+ std::chrono::steady_clock::time_point battery_sent_at_ =
+ std::chrono::steady_clock::now();
+ std::string settings_key_ = "driver_slimevr";
+
+ static vr::HmdQuaternion_t GetRotation(vr::HmdMatrix34_t &matrix);
+ static vr::HmdVector3_t GetPosition(vr::HmdMatrix34_t &matrix);
+
+ bool sent_hmd_add_message_ = false;
+
+ simdjson::ondemand::parser json_parser_;
+ std::optional default_chap_path_ = std::nullopt;
+ // std::map universes;
+
+ vr::ETrackedPropertyError last_universe_error_;
+ std::optional> current_universe_ =
+ std::nullopt;
+ std::optional SearchUniverse(std::string path,
+ uint64_t target);
+ std::optional SearchUniverses(uint64_t target);
+
+ std::optional external_left_pose_;
+ std::optional external_right_pose_;
+ std::optional last_external_left_pose_;
+ std::optional last_external_right_pose_;
+ int stale_external_left_frames_ = 0;
+ int stale_external_right_frames_ = 0;
+ // Values from slimevr_driver_config.json (fallbacks if file missing)
+ float config_external_hand_max_radius_m_ = 1.7f;
+ int config_stale_external_pose_frames_ = 1;
+ float config_pose_lerp_speed_ = 0.8f;
+ float config_pose_lerp_speed_on_swap_ = 0.25f;
+ float config_frozen_pose_position_epsilon_m_ = 0.005f;
+ void LoadDriverConfig();
+ void UpdateExternalControllerPoses();
+ static vr::DriverPose_t
+ DriverPoseFromTrackedDevicePose(const vr::TrackedDevicePose_t &raw);
+ bool ExternalPoseEquals(const vr::DriverPose_t &a,
+ const vr::DriverPose_t &b) const;
+ bool ExternalHandInFrontAndInRadius(const double hand_pos[3],
+ const vr::TrackedDevicePose_t &hmd_pose) const;
+ virtual float GetPoseLerpSpeed() override;
+ virtual float GetPoseLerpSpeedOnSwap() override;
+};
+}; // namespace SlimeVRDriver
\ No newline at end of file
diff --git a/src/bridge/ProtobufMessages.proto b/src/bridge/ProtobufMessages.proto
index b174e0a9..ab3d2cfe 100644
--- a/src/bridge/ProtobufMessages.proto
+++ b/src/bridge/ProtobufMessages.proto
@@ -58,6 +58,47 @@ message Version {
int32 protocol_version = 1;
}
+message FingerBoneRotation {
+ enum FingerBoneName {
+ THUMB_METACARPAL = 0;
+ THUMB_PROXIMAL = 1;
+ THUMB_DISTAL = 2;
+ INDEX_PROXIMAL = 3;
+ INDEX_INTERMEDIATE = 4;
+ INDEX_DISTAL = 5;
+ MIDDLE_PROXIMAL = 6;
+ MIDDLE_INTERMEDIATE = 7;
+ MIDDLE_DISTAL = 8;
+ RING_PROXIMAL = 9;
+ RING_INTERMEDIATE = 10;
+ RING_DISTAL = 11;
+ LITTLE_PROXIMAL = 12;
+ LITTLE_INTERMEDIATE = 13;
+ LITTLE_DISTAL = 14;
+ }
+ FingerBoneName name = 1;
+ float x = 2;
+ float y = 3;
+ float z = 4;
+ float w = 5;
+}
+
+message Input {
+ enum InputType {
+ DOUBLE_TAP = 0;
+ TRIPLE_TAP = 1;
+ BUTTON_1_HELD = 2;
+ BUTTON_1_UNHELD = 3;
+ BUTTON_2_HELD = 4;
+ BUTTON_2_UNHELD = 5;
+ MENU_RECENTER_HELD = 6;
+ MENU_RECENTER_UNHELD = 7;
+ STICK_CLICK_HELD = 9;
+ STICK_CLICK_UNHELD = 10;
+ }
+ InputType type = 1;
+}
+
message Position {
int32 tracker_id = 1;
optional float x = 2;
@@ -74,6 +115,25 @@ message Position {
FULL = 3;
}
optional DataSource data_source = 9;
+
+ repeated FingerBoneRotation finger_bone_rotations = 10;
+
+ repeated Input input = 11;
+}
+
+message ControllerInput {
+ int32 tracker_id = 1;
+ float thumbstick_x = 2;
+ float thumbstick_y = 3;
+ float trackpad_x = 4;
+ float trackpad_y = 5;
+ float trigger = 6;
+ float grip = 7;
+ bool button_1 = 8;
+ bool button_2 = 9;
+ bool menu_recenter = 10;
+ bool stick_click = 11;
+ bool trackpad_click = 12;
}
message UserAction {
@@ -122,5 +182,6 @@ message ProtobufMessage {
TrackerStatus tracker_status = 4;
Battery battery = 5;
Version version = 6;
+ ControllerInput controller_input = 7;
}
}
\ No newline at end of file
diff --git a/vcpkg b/vcpkg
index 9df46ca3..2fa7118f 160000
--- a/vcpkg
+++ b/vcpkg
@@ -1 +1 @@
-Subproject commit 9df46ca3f4ec3bd05984e5668218630c52ea302a
+Subproject commit 2fa7118fb2ce0c27ab73e08ab1991f4cb67af880
diff --git a/vcpkg.json b/vcpkg.json
index 2cc1da5e..7268cbf8 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -6,5 +6,6 @@
"simdjson",
"uvw",
"catch2"
- ]
+ ],
+ "builtin-baseline": "2fa7118fb2ce0c27ab73e08ab1991f4cb67af880"
}