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" }