The open source OpenXR runtime

xrt: new hand-tracking input type XRT_INPUT_HT_CONFORMING

Part-of: <https://gitlab.freedesktop.org/monado/monado/-/merge_requests/2509>

+153 -42
+2
doc/example_configs/config_v0.schema.json
··· 165 165 "XRT_INPUT_HAND_GRIP_POSE", 166 166 "XRT_INPUT_HT_UNOBSTRUCTED_RIGHT", 167 167 "XRT_INPUT_HT_UNOBSTRUCTED_LEFT", 168 + "XRT_INPUT_HT_CONFORMING_LEFT", 169 + "XRT_INPUT_HT_CONFORMING_RIGHT", 168 170 "XRT_INPUT_GENERIC_TRACKER_POSE", 169 171 "XRT_INPUT_PSMV_GRIP_POSE", 170 172 "XRT_INPUT_GO_AIM_POSE",
+2
src/xrt/auxiliary/bindings/bindings.py
··· 826 826 inputs.add("XRT_INPUT_GENERIC_HEAD_DETECT") 827 827 inputs.add("XRT_INPUT_HT_UNOBSTRUCTED_LEFT") 828 828 inputs.add("XRT_INPUT_HT_UNOBSTRUCTED_RIGHT") 829 + inputs.add("XRT_INPUT_HT_CONFORMING_LEFT") 830 + inputs.add("XRT_INPUT_HT_CONFORMING_RIGHT") 829 831 inputs.add("XRT_INPUT_GENERIC_TRACKER_POSE") 830 832 831 833 f.write('const char *\n')
+22 -2
src/xrt/auxiliary/util/u_system_helpers.h
··· 193 193 static inline struct xrt_device * 194 194 u_system_devices_get_ht_device_left(struct xrt_system_devices *xsysd) 195 195 { 196 - return u_system_devices_get_ht_device(xsysd, XRT_INPUT_HT_UNOBSTRUCTED_LEFT); 196 + const enum xrt_input_name ht_input_names[2] = { 197 + XRT_INPUT_HT_UNOBSTRUCTED_LEFT, 198 + XRT_INPUT_HT_CONFORMING_LEFT, 199 + }; 200 + for (uint32_t i = 0; i < ARRAY_SIZE(ht_input_names); ++i) { 201 + struct xrt_device *xdev = u_system_devices_get_ht_device(xsysd, ht_input_names[i]); 202 + if (xdev != NULL) { 203 + return xdev; 204 + } 205 + } 206 + return NULL; 197 207 } 198 208 199 209 /*! ··· 205 215 static inline struct xrt_device * 206 216 u_system_devices_get_ht_device_right(struct xrt_system_devices *xsysd) 207 217 { 208 - return u_system_devices_get_ht_device(xsysd, XRT_INPUT_HT_UNOBSTRUCTED_RIGHT); 218 + const enum xrt_input_name ht_input_names[2] = { 219 + XRT_INPUT_HT_UNOBSTRUCTED_RIGHT, 220 + XRT_INPUT_HT_CONFORMING_RIGHT, 221 + }; 222 + for (uint32_t i = 0; i < ARRAY_SIZE(ht_input_names); ++i) { 223 + struct xrt_device *xdev = u_system_devices_get_ht_device(xsysd, ht_input_names[i]); 224 + if (xdev != NULL) { 225 + return xdev; 226 + } 227 + } 228 + return NULL; 209 229 } 210 230 211 231
+2 -2
src/xrt/auxiliary/vive/vive_poses.c
··· 138 138 struct xrt_pose *out_offset_pose) 139 139 { 140 140 switch (input_name) { 141 - case XRT_INPUT_HT_UNOBSTRUCTED_RIGHT: 141 + case XRT_INPUT_HT_CONFORMING_LEFT: 142 142 vive_poses_get_index_hand_offset_pose(XRT_HAND_RIGHT, out_offset_pose); 143 143 return; 144 - case XRT_INPUT_HT_UNOBSTRUCTED_LEFT: 144 + case XRT_INPUT_HT_CONFORMING_RIGHT: 145 145 vive_poses_get_index_hand_offset_pose(XRT_HAND_LEFT, out_offset_pose); 146 146 return; 147 147 default: break; // Go to code below.
+46 -3
src/xrt/drivers/ht_ctrl_emu/ht_ctrl_emu.cpp
··· 106 106 return out; 107 107 } 108 108 109 + static inline bool 110 + find_best_ht_input_names(const struct xrt_device *ht_device, enum xrt_input_name ht_input_names[2]) 111 + { 112 + assert(ht_device && ht_device->inputs); 113 + 114 + if (!ht_device->supported.hand_tracking) 115 + return false; 116 + 117 + constexpr const enum xrt_input_name XRT_NULL_INPUT_NAME = (enum xrt_input_name)0; 118 + 119 + ht_input_names[0] = ht_input_names[1] = XRT_NULL_INPUT_NAME; 120 + 121 + for (uint32_t i = 0; i < ht_device->input_count; ++i) { 122 + if (ht_device->inputs[i].name == XRT_INPUT_HT_UNOBSTRUCTED_LEFT) { 123 + ht_input_names[0] = XRT_INPUT_HT_UNOBSTRUCTED_LEFT; 124 + } 125 + 126 + if (ht_device->inputs[i].name == XRT_INPUT_HT_UNOBSTRUCTED_RIGHT) { 127 + ht_input_names[1] = XRT_INPUT_HT_UNOBSTRUCTED_RIGHT; 128 + } 129 + } 130 + 131 + for (uint32_t i = 0; i < ht_device->input_count; ++i) { 132 + if (ht_input_names[0] == XRT_NULL_INPUT_NAME && 133 + ht_device->inputs[i].name == XRT_INPUT_HT_CONFORMING_LEFT) { 134 + ht_input_names[0] = XRT_INPUT_HT_CONFORMING_LEFT; 135 + } 136 + 137 + if (ht_input_names[1] == XRT_NULL_INPUT_NAME && 138 + ht_device->inputs[i].name == XRT_INPUT_HT_CONFORMING_RIGHT) { 139 + ht_input_names[1] = XRT_INPUT_HT_CONFORMING_RIGHT; 140 + } 141 + } 142 + 143 + return ht_input_names[0] != XRT_NULL_INPUT_NAME && // 144 + ht_input_names[1] != XRT_NULL_INPUT_NAME; 145 + } 146 + 109 147 static inline struct cemu_device * 110 148 cemu_device(struct xrt_device *xdev) 111 149 { ··· 417 455 extern "C" int 418 456 cemu_devices_create(struct xrt_device *head, struct xrt_device *hands, struct xrt_device **out_xdevs) 419 457 { 458 + enum xrt_input_name ht_input_names[2]; 459 + if (!find_best_ht_input_names(hands, ht_input_names)) { 460 + U_LOG_E("\"hands\" parameter is not a hand-tracking device or does have expected inputs."); 461 + return 0; 462 + } 463 + 420 464 enum u_device_alloc_flags flags = U_DEVICE_ALLOC_NO_FLAGS; 421 465 422 466 struct cemu_device *cemud[2]; ··· 442 486 cemud[i]->base.supported.position_tracking = true; 443 487 444 488 445 - cemud[i]->base.inputs[CEMU_INDEX_HAND_TRACKING].name = 446 - i ? XRT_INPUT_HT_UNOBSTRUCTED_RIGHT : XRT_INPUT_HT_UNOBSTRUCTED_LEFT; 489 + cemud[i]->base.inputs[CEMU_INDEX_HAND_TRACKING].name = ht_input_names[i]; 447 490 cemud[i]->base.inputs[CEMU_INDEX_SELECT].name = XRT_INPUT_SIMPLE_SELECT_CLICK; 448 491 cemud[i]->base.inputs[CEMU_INDEX_MENU].name = XRT_INPUT_SIMPLE_MENU_CLICK; 449 492 cemud[i]->base.inputs[CEMU_INDEX_GRIP].name = XRT_INPUT_SIMPLE_GRIP_POSE; ··· 469 512 CEMU_WARN(cemud[i], "serial truncated: %s", cemud[i]->base.str); 470 513 } 471 514 472 - cemud[i]->ht_input_name = i ? XRT_INPUT_HT_UNOBSTRUCTED_RIGHT : XRT_INPUT_HT_UNOBSTRUCTED_LEFT; 515 + cemud[i]->ht_input_name = ht_input_names[i]; 473 516 474 517 cemud[i]->hand_index = i; 475 518 system->out_hand[i] = cemud[i];
+3 -3
src/xrt/drivers/remote/r_device.c
··· 147 147 struct r_device *rd = r_device(xdev); 148 148 struct r_hub *r = rd->r; 149 149 150 - if (name != XRT_INPUT_HT_UNOBSTRUCTED_LEFT && name != XRT_INPUT_HT_UNOBSTRUCTED_RIGHT) { 150 + if (name != XRT_INPUT_HT_CONFORMING_LEFT && name != XRT_INPUT_HT_CONFORMING_RIGHT) { 151 151 U_LOG_XDEV_UNSUPPORTED_INPUT(&rd->base, u_log_get_global_level(), name); 152 152 return XRT_ERROR_INPUT_UNSUPPORTED; 153 153 } ··· 236 236 rd->base.inputs[17].name = XRT_INPUT_INDEX_GRIP_POSE; 237 237 rd->base.inputs[18].name = XRT_INPUT_INDEX_AIM_POSE; 238 238 if (is_left) { 239 - rd->base.inputs[19].name = XRT_INPUT_HT_UNOBSTRUCTED_LEFT; 239 + rd->base.inputs[19].name = XRT_INPUT_HT_CONFORMING_LEFT; 240 240 } else { 241 - rd->base.inputs[19].name = XRT_INPUT_HT_UNOBSTRUCTED_RIGHT; 241 + rd->base.inputs[19].name = XRT_INPUT_HT_CONFORMING_RIGHT; 242 242 } 243 243 rd->base.inputs[20].name = XRT_INPUT_GENERIC_PALM_POSE; 244 244
+3 -3
src/xrt/drivers/steamvr_lh/device.cpp
··· 261 261 finger_inputs_map.insert({path, &finger_inputs_vec.back()}); 262 262 } 263 263 assert(inputs_vec.capacity() >= inputs_vec.size() + 1); 264 - inputs_vec.push_back({true, 0, XRT_INPUT_HT_UNOBSTRUCTED_LEFT, {}}); 264 + inputs_vec.push_back({true, 0, XRT_INPUT_HT_CONFORMING_LEFT, {}}); 265 265 inputs_map.insert({std::string_view("HAND"), &inputs_vec.back()}); 266 266 } 267 267 ··· 875 875 } 876 876 case vr::TrackedControllerRole_RightHand: { 877 877 this->device_type = XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER; 878 - set_hand_tracking_hand(XRT_INPUT_HT_UNOBSTRUCTED_RIGHT); 878 + set_hand_tracking_hand(XRT_INPUT_HT_CONFORMING_RIGHT); 879 879 break; 880 880 } 881 881 case vr::TrackedControllerRole_LeftHand: { 882 882 this->device_type = XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER; 883 - set_hand_tracking_hand(XRT_INPUT_HT_UNOBSTRUCTED_LEFT); 883 + set_hand_tracking_hand(XRT_INPUT_HT_CONFORMING_LEFT); 884 884 break; 885 885 } 886 886 case vr::TrackedControllerRole_OptOut: {
+3 -3
src/xrt/drivers/survive/survive_driver.c
··· 468 468 { 469 469 struct survive_device *survive = (struct survive_device *)xdev; 470 470 471 - if (name != XRT_INPUT_HT_UNOBSTRUCTED_LEFT && name != XRT_INPUT_HT_UNOBSTRUCTED_RIGHT) { 471 + if (name != XRT_INPUT_HT_CONFORMING_LEFT && name != XRT_INPUT_HT_CONFORMING_RIGHT) { 472 472 U_LOG_XDEV_UNSUPPORTED_INPUT(&survive->base, survive->sys->log_level, name); 473 473 return XRT_ERROR_INPUT_UNSUPPORTED; 474 474 } ··· 1136 1136 1137 1137 if (variant == CONTROLLER_INDEX_LEFT) { 1138 1138 survive->base.device_type = XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER; 1139 - survive->base.inputs[VIVE_CONTROLLER_HAND_TRACKING].name = XRT_INPUT_HT_UNOBSTRUCTED_LEFT; 1139 + survive->base.inputs[VIVE_CONTROLLER_HAND_TRACKING].name = XRT_INPUT_HT_CONFORMING_LEFT; 1140 1140 snprintf(survive->base.str, XRT_DEVICE_NAME_LEN, "Valve Index Left Controller (libsurvive)"); 1141 1141 } else if (variant == CONTROLLER_INDEX_RIGHT) { 1142 1142 survive->base.device_type = XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER; 1143 - survive->base.inputs[VIVE_CONTROLLER_HAND_TRACKING].name = XRT_INPUT_HT_UNOBSTRUCTED_RIGHT; 1143 + survive->base.inputs[VIVE_CONTROLLER_HAND_TRACKING].name = XRT_INPUT_HT_CONFORMING_RIGHT; 1144 1144 snprintf(survive->base.str, XRT_DEVICE_NAME_LEN, "Valve Index Right Controller (libsurvive)"); 1145 1145 } 1146 1146
+3 -3
src/xrt/drivers/vive/vive_controller.c
··· 375 375 376 376 struct vive_controller_device *d = vive_controller_device(xdev); 377 377 378 - if (name != XRT_INPUT_HT_UNOBSTRUCTED_LEFT && name != XRT_INPUT_HT_UNOBSTRUCTED_RIGHT) { 378 + if (name != XRT_INPUT_HT_CONFORMING_LEFT && name != XRT_INPUT_HT_CONFORMING_LEFT) { 379 379 U_LOG_XDEV_UNSUPPORTED_INPUT(&d->base, d->log_level, name); 380 380 return XRT_ERROR_INPUT_UNSUPPORTED; 381 381 } ··· 1181 1181 1182 1182 if (d->config.variant == CONTROLLER_INDEX_LEFT) { 1183 1183 d->base.device_type = XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER; 1184 - d->base.inputs[VIVE_CONTROLLER_HAND_TRACKING].name = XRT_INPUT_HT_UNOBSTRUCTED_LEFT; 1184 + d->base.inputs[VIVE_CONTROLLER_HAND_TRACKING].name = XRT_INPUT_HT_CONFORMING_LEFT; 1185 1185 snprintf(d->base.str, XRT_DEVICE_NAME_LEN, "Valve Index Left Controller (vive)"); 1186 1186 } else if (d->config.variant == CONTROLLER_INDEX_RIGHT) { 1187 1187 d->base.device_type = XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER; 1188 - d->base.inputs[VIVE_CONTROLLER_HAND_TRACKING].name = XRT_INPUT_HT_UNOBSTRUCTED_RIGHT; 1188 + d->base.inputs[VIVE_CONTROLLER_HAND_TRACKING].name = XRT_INPUT_HT_CONFORMING_RIGHT; 1189 1189 snprintf(d->base.str, XRT_DEVICE_NAME_LEN, "Valve Index Right Controller (vive)"); 1190 1190 } 1191 1191 } else if (d->config.variant == CONTROLLER_TRACKER_GEN1) {
+10 -8
src/xrt/include/xrt/xrt_defines.h
··· 881 881 _(XRT_INPUT_GENERIC_HEAD_POSE , XRT_INPUT_NAME(0x0000, POSE)) \ 882 882 _(XRT_INPUT_GENERIC_HEAD_DETECT , XRT_INPUT_NAME(0x0001, BOOLEAN)) \ 883 883 _(XRT_INPUT_HT_UNOBSTRUCTED_LEFT , XRT_INPUT_NAME(0x0002, HAND_TRACKING)) \ 884 - _(XRT_INPUT_HT_UNOBSTRUCTED_RIGHT , XRT_INPUT_NAME(0x0004, HAND_TRACKING)) \ 885 - _(XRT_INPUT_GENERIC_TRACKER_POSE , XRT_INPUT_NAME(0x0005, POSE)) \ 884 + _(XRT_INPUT_HT_UNOBSTRUCTED_RIGHT , XRT_INPUT_NAME(0x0003, HAND_TRACKING)) \ 885 + _(XRT_INPUT_HT_CONFORMING_LEFT , XRT_INPUT_NAME(0x0004, HAND_TRACKING)) \ 886 + _(XRT_INPUT_HT_CONFORMING_RIGHT , XRT_INPUT_NAME(0x0005, HAND_TRACKING)) \ 887 + _(XRT_INPUT_GENERIC_TRACKER_POSE , XRT_INPUT_NAME(0x0006, POSE)) \ 886 888 /** XR_EXT_palm_pose */ \ 887 - _(XRT_INPUT_GENERIC_PALM_POSE , XRT_INPUT_NAME(0x0006, POSE)) \ 889 + _(XRT_INPUT_GENERIC_PALM_POSE , XRT_INPUT_NAME(0x0007, POSE)) \ 888 890 \ 889 891 /** XR_EXT_eye_gaze_interaction */ \ 890 - _(XRT_INPUT_GENERIC_EYE_GAZE_POSE , XRT_INPUT_NAME(0x0007, POSE)) \ 892 + _(XRT_INPUT_GENERIC_EYE_GAZE_POSE , XRT_INPUT_NAME(0x0008, POSE)) \ 891 893 /** Standard non-view reference spaces */ \ 892 - _(XRT_INPUT_GENERIC_LOCAL_SPACE_POSE , XRT_INPUT_NAME(0x0008, POSE)) \ 893 - _(XRT_INPUT_GENERIC_LOCAL_FLOOR_SPACE_POSE , XRT_INPUT_NAME(0x0009, POSE)) \ 894 - _(XRT_INPUT_GENERIC_STAGE_SPACE_POSE , XRT_INPUT_NAME(0x000A, POSE)) \ 895 - _(XRT_INPUT_GENERIC_UNBOUNDED_SPACE_POSE , XRT_INPUT_NAME(0x000B, POSE)) \ 894 + _(XRT_INPUT_GENERIC_LOCAL_SPACE_POSE , XRT_INPUT_NAME(0x0009, POSE)) \ 895 + _(XRT_INPUT_GENERIC_LOCAL_FLOOR_SPACE_POSE , XRT_INPUT_NAME(0x000A, POSE)) \ 896 + _(XRT_INPUT_GENERIC_STAGE_SPACE_POSE , XRT_INPUT_NAME(0x000B, POSE)) \ 897 + _(XRT_INPUT_GENERIC_UNBOUNDED_SPACE_POSE , XRT_INPUT_NAME(0x000C, POSE)) \ 896 898 \ 897 899 _(XRT_INPUT_SIMPLE_SELECT_CLICK , XRT_INPUT_NAME(0x0010, BOOLEAN)) \ 898 900 _(XRT_INPUT_SIMPLE_MENU_CLICK , XRT_INPUT_NAME(0x0011, BOOLEAN)) \
+15 -1
src/xrt/state_trackers/oxr/oxr_api_session.c
··· 442 442 // Find the correct input on the device. 443 443 if (xdev != NULL && xdev->supported.hand_tracking) { 444 444 for (uint32_t j = 0; j < xdev->input_count; j++) { 445 - struct xrt_input *input = &xdev->inputs[j]; 445 + const struct xrt_input *input = &xdev->inputs[j]; 446 446 447 447 if ((input->name == XRT_INPUT_HT_UNOBSTRUCTED_LEFT && createInfo->hand == XR_HAND_LEFT_EXT) || 448 448 (input->name == XRT_INPUT_HT_UNOBSTRUCTED_RIGHT && createInfo->hand == XR_HAND_RIGHT_EXT)) { 449 449 hand_tracker->xdev = xdev; 450 450 hand_tracker->input_name = input->name; 451 451 break; 452 + } 453 + } 454 + if (hand_tracker->xdev == NULL) { 455 + for (uint32_t j = 0; j < xdev->input_count; j++) { 456 + const struct xrt_input *input = &xdev->inputs[j]; 457 + 458 + if ((input->name == XRT_INPUT_HT_CONFORMING_LEFT && 459 + createInfo->hand == XR_HAND_LEFT_EXT) || 460 + (input->name == XRT_INPUT_HT_CONFORMING_RIGHT && 461 + createInfo->hand == XR_HAND_RIGHT_EXT)) { 462 + hand_tracker->xdev = xdev; 463 + hand_tracker->input_name = input->name; 464 + break; 465 + } 452 466 } 453 467 } 454 468 }
+42 -14
src/xrt/state_trackers/steamvr_drv/ovrd_driver.cpp
··· 12 12 */ 13 13 14 14 #include <cstring> 15 + #include <cassert> 16 + #include <array> 15 17 #include <thread> 16 18 17 19 #include "math/m_api.h" ··· 966 968 } 967 969 968 970 if (m_xdev->supported.hand_tracking && m_skeletal_input_control.control_handle) { 969 - vr::VRBoneTransform_t bone_transforms[OPENVR_BONE_COUNT]; 971 + constexpr const std::size_t sk_comp_size = 2; 970 972 971 - timepoint_ns now_ns = os_monotonic_get_ns(); 972 - struct xrt_hand_joint_set out_joint_set_value; 973 - int64_t out_timestamp_ns; 973 + using ht_input_name_list = std::array<const enum xrt_input_name, sk_comp_size>; 974 + constexpr const std::array<const ht_input_name_list, sk_comp_size> ht_input_name_map = { 975 + ht_input_name_list{ 976 + XRT_INPUT_HT_UNOBSTRUCTED_LEFT, 977 + XRT_INPUT_HT_CONFORMING_LEFT, 978 + }, 979 + ht_input_name_list{ 980 + XRT_INPUT_HT_UNOBSTRUCTED_RIGHT, 981 + XRT_INPUT_HT_CONFORMING_RIGHT, 982 + }, 983 + }; 984 + using bone_transform_list = std::array<vr::VRBoneTransform_t, OPENVR_BONE_COUNT>; 985 + using bone_transforms_list = std::vector<bone_transform_list>; 974 986 975 - m_xdev->get_hand_tracking(m_xdev, 976 - m_hand == XRT_HAND_LEFT ? XRT_INPUT_HT_UNOBSTRUCTED_LEFT 977 - : XRT_INPUT_HT_UNOBSTRUCTED_RIGHT, 978 - now_ns, &out_joint_set_value, &out_timestamp_ns); 987 + bone_transforms_list bone_transforms; 988 + bone_transforms.reserve(sk_comp_size); 989 + const auto &ht_input_names = ht_input_name_map[m_hand]; 990 + const timepoint_ns now_ns = os_monotonic_get_ns(); 979 991 980 - hand_joint_set_to_bone_transform(out_joint_set_value, bone_transforms, m_hand); 981 - // hand_joint_set_to_bone_transforms(out_joint_set_value, bone_transforms); 992 + for (size_t i = 0; i < ht_input_names.size(); ++i) { 993 + int64_t out_timestamp_ns; 994 + struct xrt_hand_joint_set joint_set_value = {}; 995 + if (m_xdev->get_hand_tracking(m_xdev, ht_input_names[i], now_ns, &joint_set_value, 996 + &out_timestamp_ns) != XRT_SUCCESS) { 997 + continue; 998 + } 999 + auto &bts = bone_transforms.emplace_back(); 1000 + hand_joint_set_to_bone_transform(joint_set_value, bts.data(), m_hand); 1001 + // hand_joint_set_to_bone_transforms(out_joint_set_value, bone_transforms); 1002 + } 1003 + 1004 + if (bone_transforms.empty()) 1005 + return; 1006 + if (bone_transforms.size() < sk_comp_size) { 1007 + bone_transforms.push_back(bone_transforms.back()); 1008 + } 1009 + assert(bone_transforms.size() >= sk_comp_size); 982 1010 983 1011 vr::EVRInputError err = vr::VRDriverInput()->UpdateSkeletonComponent( 984 1012 m_skeletal_input_control.control_handle, vr::VRSkeletalMotionRange_WithoutController, 985 - bone_transforms, OPENVR_BONE_COUNT); 1013 + bone_transforms[0].data(), OPENVR_BONE_COUNT); 986 1014 if (err != vr::VRInputError_None) { 987 1015 ovrd_log("error updating skeleton: %i ", err); 988 1016 } 989 1017 990 - err = vr::VRDriverInput()->UpdateSkeletonComponent(m_skeletal_input_control.control_handle, 991 - vr::VRSkeletalMotionRange_WithController, 992 - bone_transforms, OPENVR_BONE_COUNT); 1018 + err = vr::VRDriverInput()->UpdateSkeletonComponent( 1019 + m_skeletal_input_control.control_handle, vr::VRSkeletalMotionRange_WithController, 1020 + bone_transforms[1].data(), OPENVR_BONE_COUNT); 993 1021 if (err != vr::VRInputError_None) { 994 1022 ovrd_log("error updating skeleton: %i ", err); 995 1023 }