From 2184fa96985d459f10793f3569f2ca96cb57f839 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 20 Feb 2024 08:56:59 -0600 Subject: [PATCH] Provide generic interface for XR hand tracking --- doc/classes/XRHandModifier3D.xml | 34 ++ doc/classes/XRHandTracker.xml | 223 +++++++++++++ doc/classes/XRServer.xml | 48 +++ modules/openxr/doc_classes/OpenXRHand.xml | 2 +- .../openxr/doc_classes/OpenXRInterface.xml | 14 +- .../openxr_hand_tracking_extension.cpp | 68 ++++ .../openxr_hand_tracking_extension.h | 2 + scene/3d/xr_hand_modifier_3d.cpp | 309 ++++++++++++++++++ scene/3d/xr_hand_modifier_3d.h | 87 +++++ scene/register_scene_types.cpp | 2 + servers/register_server_types.cpp | 2 + servers/xr/xr_hand_tracker.cpp | 179 ++++++++++ servers/xr/xr_hand_tracker.h | 137 ++++++++ servers/xr_server.cpp | 48 +++ servers/xr_server.h | 11 +- 15 files changed, 1157 insertions(+), 9 deletions(-) create mode 100644 doc/classes/XRHandModifier3D.xml create mode 100644 doc/classes/XRHandTracker.xml create mode 100644 scene/3d/xr_hand_modifier_3d.cpp create mode 100644 scene/3d/xr_hand_modifier_3d.h create mode 100644 servers/xr/xr_hand_tracker.cpp create mode 100644 servers/xr/xr_hand_tracker.h diff --git a/doc/classes/XRHandModifier3D.xml b/doc/classes/XRHandModifier3D.xml new file mode 100644 index 00000000000..7e08d3f6876 --- /dev/null +++ b/doc/classes/XRHandModifier3D.xml @@ -0,0 +1,34 @@ + + + + A node for driving hand meshes from [XRHandTracker] data. + + + This node uses hand tracking data from a [XRHandTracker] to animate the skeleton of a hand mesh. + + + $DOCS_URL/tutorials/xr/index.html + + + + Specifies the type of updates to perform on the bones. + + + The name of the [XRHandTracker] registered with [XRServer] to obtain the hand tracking data from. + + + A [NodePath] to a [Skeleton3D] to animate. + + + + + The skeleton's bones are fully updated (both position and rotation) to match the tracked bones. + + + The skeleton's bones are only rotated to align with the tracked bones, preserving bone length. + + + Represents the size of the [enum BoneUpdate] enum. + + + diff --git a/doc/classes/XRHandTracker.xml b/doc/classes/XRHandTracker.xml new file mode 100644 index 00000000000..932fec083a2 --- /dev/null +++ b/doc/classes/XRHandTracker.xml @@ -0,0 +1,223 @@ + + + + A tracked hand in XR. + + + A hand tracking system will create an instance of this object and add it to the [XRServer]. This tracking system will then obtain skeleton data, convert it to the Godot Humanoid hand skeleton and store this data on the [XRHandTracker] object. + Use [XRHandModifier3D] to animate a hand mesh using hand tracking data. + + + $DOCS_URL/tutorials/xr/index.html + + + + + + + Returns the angular velocity for the given hand joint. + + + + + + + Returns flags about the validity of the tracking data for the given hand joint (see [enum XRHandTracker.HandJointFlags]). + + + + + + + Returns the linear velocity for the given hand joint. + + + + + + + Returns the radius of the given hand joint. + + + + + + + Returns the transform for the given hand joint. + + + + + + + + Sets the angular velocity for the given hand joint. + + + + + + + + Sets flags about the validity of the tracking data for the given hand joint. + + + + + + + + Sets the linear velocity for the given hand joint. + + + + + + + + Sets the radius of the given hand joint. + + + + + + + + Sets the transform for the given hand joint. + + + + + + The type of hand. + + + The source of the hand tracking data. + + + If [code]true[/code], the hand tracking data is valid. + + + + + A left hand. + + + A right hand. + + + Represents the size of the [enum Hand] enum. + + + The source of hand tracking data is unknown. + + + The source of hand tracking data is unobstructed, meaning that an accurate method of hand tracking is used. These include optical hand tracking, data gloves, etc. + + + The source of hand tracking data is a controller, meaning that joint positions are inferred from controller inputs. + + + Represents the size of the [enum HandTrackingSource] enum. + + + Palm joint. + + + Wrist joint. + + + Thumb metacarpal joint. + + + Thumb phalanx proximal joint. + + + Thumb phalanx distal joint. + + + Thumb tip joint. + + + Index finger metacarpal joint. + + + Index finger phalanx proximal joint. + + + Index finger phalanx intermediate joint. + + + Index finger phalanx distal joint. + + + Index finger tip joint. + + + Middle finger metacarpal joint. + + + Middle finger phalanx proximal joint. + + + Middle finger phalanx intermediate joint. + + + Middle finger phalanx distal joint. + + + Middle finger tip joint. + + + Ring finger metacarpal joint. + + + Ring finger phalanx proximal joint. + + + Ring finger phalanx intermediate joint. + + + Ring finger phalanx distal joint. + + + Ring finger tip joint. + + + Pinky finger metacarpal joint. + + + Pinky finger phalanx proximal joint. + + + Pinky finger phalanx intermediate joint. + + + Pinky finger phalanx distal joint. + + + Pinky finger tip joint. + + + Represents the size of the [enum HandJoint] enum. + + + The hand joint's orientation data is valid. + + + The hand joint's orientation is actively tracked. May not be set if tracking has been temporarily lost. + + + The hand joint's position data is valid. + + + The hand joint's position is actively tracked. May not be set if tracking has been temporarily lost. + + + The hand joint's linear velocity data is valid. + + + The hand joint's angular velocity data is valid. + + + diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index f98c1d66a48..09e14f1b210 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -18,6 +18,14 @@ Registers a new [XRFaceTracker] that tracks the blend shapes of a face. + + + + + + Registers a new [XRHandTracker] that tracks the joints of a hand. + + @@ -71,6 +79,19 @@ Returns a dictionary of the registered face trackers. Each element of the dictionary is a tracker name mapping to the [XRFaceTracker] instance. + + + + + Returns the [XRHandTracker] with the given tracker name. + + + + + + Returns a dictionary of the registered hand trackers. Each element of the dictionary is a tracker name mapping to the [XRHandTracker] instance. + + @@ -123,6 +144,13 @@ Removes a registered [XRFaceTracker]. + + + + + Removes a registered [XRHandTracker]. + + @@ -171,6 +199,26 @@ Emitted when an existing face tracker is updated. + + + + + Emitted when a new hand tracker is added. + + + + + + Emitted when a hand tracker is removed. + + + + + + + Emitted when an existing hand tracker is updated. + + diff --git a/modules/openxr/doc_classes/OpenXRHand.xml b/modules/openxr/doc_classes/OpenXRHand.xml index 1c4da831388..23d932ddd75 100644 --- a/modules/openxr/doc_classes/OpenXRHand.xml +++ b/modules/openxr/doc_classes/OpenXRHand.xml @@ -1,5 +1,5 @@ - + Node supporting hand and finger tracking in OpenXR. diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index caaaeb90ffa..5d38788dcd0 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -23,7 +23,7 @@ Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the OpenXR runtime and after the interface has been initialized. - + @@ -31,7 +31,7 @@ If handtracking is enabled, returns the angular velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D]! - + @@ -39,7 +39,7 @@ If handtracking is enabled, returns flags that inform us of the validity of the tracking data. - + @@ -47,7 +47,7 @@ If handtracking is enabled, returns the linear velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied! - + @@ -55,7 +55,7 @@ If handtracking is enabled, returns the position of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied! - + @@ -63,7 +63,7 @@ If handtracking is enabled, returns the radius of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is without worldscale applied! - + @@ -71,7 +71,7 @@ If handtracking is enabled, returns the rotation of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. - + diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index 884fb41a3cf..b3c20ef8b91 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -193,11 +193,18 @@ void OpenXRHandTrackingExtension::on_process() { hand_trackers[i].locations.jointCount = XR_HAND_JOINT_COUNT_EXT; hand_trackers[i].locations.jointLocations = hand_trackers[i].joint_locations; + Ref godot_tracker; + godot_tracker.instantiate(); + godot_tracker->set_hand(i == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT); + XRServer::get_singleton()->add_hand_tracker(i == 0 ? "/user/left" : "/user/right", godot_tracker); + hand_trackers[i].godot_tracker = godot_tracker; + hand_trackers[i].is_initialized = true; } } if (hand_trackers[i].is_initialized) { + Ref godot_tracker = hand_trackers[i].godot_tracker; void *next_pointer = nullptr; XrHandJointsMotionRangeInfoEXT motion_range_info = { XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT, next_pointer, hand_trackers[i].motion_range }; @@ -216,6 +223,7 @@ void OpenXRHandTrackingExtension::on_process() { if (XR_FAILED(result)) { // not successful? then we do nothing. print_line("OpenXR: Failed to get tracking for hand", i, "[", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + godot_tracker->set_has_tracking_data(false); continue; } @@ -225,6 +233,64 @@ void OpenXRHandTrackingExtension::on_process() { !hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) { hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive } + + if (hand_trackers[i].locations.isActive) { + godot_tracker->set_has_tracking_data(true); + + // SKELETON_RIG_HUMANOID bone adjustment. This rotation performs: + // OpenXR Z+ -> Godot Humanoid Y- (Back along the bone) + // OpenXR Y+ -> Godot Humanoid Z- (Out the back of the hand) + const Quaternion bone_adjustment(0.0, -Math_SQRT12, Math_SQRT12, 0.0); + + for (int joint = 0; joint < XR_HAND_JOINT_COUNT_EXT; joint++) { + const XrHandJointLocationEXT &location = hand_trackers[i].joint_locations[joint]; + const XrHandJointVelocityEXT &velocity = hand_trackers[i].joint_velocities[joint]; + const XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source; + const XrPosef &pose = location.pose; + + Transform3D transform; + BitField flags; + + if (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { + if (pose.orientation.x != 0 || pose.orientation.y != 0 || pose.orientation.z != 0 || pose.orientation.w != 0) { + flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID); + transform.basis = Basis(Quaternion(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w) * bone_adjustment); + } + } + if (location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_POSITION_VALID); + transform.origin = Vector3(pose.position.x, pose.position.y, pose.position.z); + } + if (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) { + flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_TRACKED); + } + if (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) { + flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_POSITION_TRACKED); + } + if (location.locationFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { + flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_LINEAR_VELOCITY_VALID); + godot_tracker->set_hand_joint_linear_velocity((XRHandTracker::HandJoint)joint, Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z)); + } + if (location.locationFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) { + flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID); + godot_tracker->set_hand_joint_angular_velocity((XRHandTracker::HandJoint)joint, Vector3(velocity.angularVelocity.x, velocity.angularVelocity.y, velocity.angularVelocity.z)); + } + + godot_tracker->set_hand_joint_flags((XRHandTracker::HandJoint)joint, flags); + godot_tracker->set_hand_joint_transform((XRHandTracker::HandJoint)joint, transform); + godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius); + + XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; + if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; + } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; + } + godot_tracker->set_hand_tracking_source(source); + } + } else { + godot_tracker->set_has_tracking_data(false); + } } } } @@ -244,6 +310,8 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() { hand_trackers[i].is_initialized = false; hand_trackers[i].hand_tracker = XR_NULL_HANDLE; + + XRServer::get_singleton()->remove_hand_tracker(i == 0 ? "/user/left" : "/user/right"); } } } diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h index 967538b3777..f709bc05c21 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.h +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h @@ -34,6 +34,7 @@ #include "../util.h" #include "core/math/quaternion.h" #include "openxr_extension_wrapper.h" +#include "servers/xr/xr_hand_tracker.h" class OpenXRHandTrackingExtension : public OpenXRExtensionWrapper { public: @@ -52,6 +53,7 @@ public: struct HandTracker { bool is_initialized = false; + Ref godot_tracker; XrHandJointsMotionRangeEXT motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; HandTrackedSource source = OPENXR_SOURCE_UNKNOWN; diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp new file mode 100644 index 00000000000..3c385197132 --- /dev/null +++ b/scene/3d/xr_hand_modifier_3d.cpp @@ -0,0 +1,309 @@ +/**************************************************************************/ +/* xr_hand_modifier_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "xr_hand_modifier_3d.h" + +#include "scene/3d/skeleton_3d.h" +#include "servers/xr/xr_pose.h" +#include "servers/xr_server.h" + +void XRHandModifier3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_hand_tracker", "tracker_name"), &XRHandModifier3D::set_hand_tracker); + ClassDB::bind_method(D_METHOD("get_hand_tracker"), &XRHandModifier3D::get_hand_tracker); + + ClassDB::bind_method(D_METHOD("set_target", "target"), &XRHandModifier3D::set_target); + ClassDB::bind_method(D_METHOD("get_target"), &XRHandModifier3D::get_target); + + ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRHandModifier3D::set_bone_update); + ClassDB::bind_method(D_METHOD("get_bone_update"), &XRHandModifier3D::get_bone_update); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/left,/user/right"), "set_hand_tracker", "get_hand_tracker"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_target", "get_target"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update"); + + BIND_ENUM_CONSTANT(BONE_UPDATE_FULL); + BIND_ENUM_CONSTANT(BONE_UPDATE_ROTATION_ONLY); + BIND_ENUM_CONSTANT(BONE_UPDATE_MAX); +} + +void XRHandModifier3D::set_hand_tracker(const StringName &p_tracker_name) { + tracker_name = p_tracker_name; +} + +StringName XRHandModifier3D::get_hand_tracker() const { + return tracker_name; +} + +void XRHandModifier3D::set_target(const NodePath &p_target) { + target = p_target; + + if (is_inside_tree()) { + _get_joint_data(); + } +} + +NodePath XRHandModifier3D::get_target() const { + return target; +} + +void XRHandModifier3D::set_bone_update(BoneUpdate p_bone_update) { + ERR_FAIL_INDEX(p_bone_update, BONE_UPDATE_MAX); + bone_update = p_bone_update; +} + +XRHandModifier3D::BoneUpdate XRHandModifier3D::get_bone_update() const { + return bone_update; +} + +Skeleton3D *XRHandModifier3D::get_skeleton() { + if (!has_node(target)) { + return nullptr; + } + + Node *node = get_node(target); + if (!node) { + return nullptr; + } + + Skeleton3D *skeleton = Object::cast_to(node); + return skeleton; +} + +void XRHandModifier3D::_get_joint_data() { + // Table of bone names for different rig types. + static const String bone_names[XRHandTracker::HAND_JOINT_MAX] = { + "Palm", + "Hand", + "ThumbMetacarpal", + "ThumbProximal", + "ThumbDistal", + "ThumbTip", + "IndexMetacarpal", + "IndexProximal", + "IndexIntermediate", + "IndexDistal", + "IndexTip", + "MiddleMetacarpal", + "MiddleProximal", + "MiddleIntermediate", + "MiddleDistal", + "MiddleTip", + "RingMetacarpal", + "RingProximal", + "RingIntermediate", + "RingDistal", + "RingTip", + "LittleMetacarpal", + "LittleProximal", + "LittleIntermediate", + "LittleDistal", + "LittleTip", + }; + + static const String bone_name_format[2] = { + "Left", + "Right", + }; + + // reset JIC + for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) { + joints[i].bone = -1; + joints[i].parent_joint = -1; + } + + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + + XRServer *xr_server = XRServer::get_singleton(); + if (!xr_server) { + return; + } + + Ref tracker = xr_server->get_hand_tracker(tracker_name); + if (tracker.is_null()) { + return; + } + + XRHandTracker::Hand hand = tracker->get_hand(); + + // Find the skeleton-bones associated with each joint. + int bones[XRHandTracker::HAND_JOINT_MAX]; + for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) { + // Construct the expected bone name. + String bone_name = bone_name_format[hand].replace("", bone_names[i]); + + // Find the skeleton bone. + bones[i] = skeleton->find_bone(bone_name); + if (bones[i] == -1) { + WARN_PRINT(vformat("Couldn't obtain bone for %s", bone_name)); + } + } + + // Assemble the joint relationship to the available skeleton bones. + for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) { + // Get the skeleton bone (skip if not found). + const int bone = bones[i]; + if (bone == -1) { + continue; + } + + // Find the parent skeleton-bone. + const int parent_bone = skeleton->get_bone_parent(bone); + if (parent_bone == -1) { + // If no parent skeleton-bone exists then drive this relative to palm joint. + joints[i].bone = bone; + joints[i].parent_joint = XRHandTracker::HAND_JOINT_PALM; + continue; + } + + // Find the joint associated with the parent skeleton-bone. + for (int j = 0; j < XRHandTracker::HAND_JOINT_MAX; ++j) { + if (bones[j] == parent_bone) { + // If a parent joint is found then drive this bone relative to it. + joints[i].bone = bone; + joints[i].parent_joint = j; + break; + } + } + } +} + +void XRHandModifier3D::_update_skeleton() { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + + XRServer *xr_server = XRServer::get_singleton(); + if (!xr_server) { + return; + } + + Ref tracker = xr_server->get_hand_tracker(tracker_name); + if (tracker.is_null()) { + return; + } + + // We cache our transforms so we can quickly calculate local transforms. + bool has_valid_data[XRHandTracker::HAND_JOINT_MAX]; + Transform3D transforms[XRHandTracker::HAND_JOINT_MAX]; + Transform3D inv_transforms[XRHandTracker::HAND_JOINT_MAX]; + + const float ws = xr_server->get_world_scale(); + + if (tracker->get_has_tracking_data()) { + for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { + BitField flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint); + has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID); + + if (has_valid_data[joint]) { + transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint); + transforms[joint].origin *= ws; + inv_transforms[joint] = transforms[joint].inverse(); + } + } + + if (has_valid_data[XRHandTracker::HAND_JOINT_PALM]) { + for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { + // Get the skeleton bone (skip if none). + const int bone = joints[joint].bone; + if (bone == -1) { + continue; + } + + // Calculate the relative relationship to the parent bone joint. + const int parent_joint = joints[joint].parent_joint; + const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint]; + + // Update the bone position if enabled by update mode. + if (bone_update == BONE_UPDATE_FULL) { + skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin); + } + + // Always update the bone rotation. + skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis)); + } + + // Transform to the skeleton pose. + set_transform(transforms[XRHandTracker::HAND_JOINT_PALM]); + + set_visible(true); + } else { + set_visible(false); + } + } else { + set_visible(false); + } +} + +void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, const Ref &p_tracker) { + if (tracker_name == p_tracker_name) { + _get_joint_data(); + } +} + +void XRHandModifier3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + xr_server->connect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->connect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->connect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref())); + } + + _get_joint_data(); + + set_process_internal(true); + } break; + case NOTIFICATION_EXIT_TREE: { + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + xr_server->disconnect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->disconnect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->disconnect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref())); + } + + set_process_internal(false); + + for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) { + joints[i].bone = -1; + joints[i].parent_joint = -1; + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + _update_skeleton(); + } break; + default: { + } break; + } +} diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h new file mode 100644 index 00000000000..2bc30d42d49 --- /dev/null +++ b/scene/3d/xr_hand_modifier_3d.h @@ -0,0 +1,87 @@ +/**************************************************************************/ +/* xr_hand_modifier_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef XR_HAND_MODIFIER_3D_H +#define XR_HAND_MODIFIER_3D_H + +#include "scene/3d/node_3d.h" +#include "servers/xr/xr_hand_tracker.h" + +class Skeleton3D; + +/** + The XRHandModifier3D node drives a hand skeleton using hand tracking + data from an XRHandTracking instance. + */ + +class XRHandModifier3D : public Node3D { + GDCLASS(XRHandModifier3D, Node3D); + +public: + enum BoneUpdate { + BONE_UPDATE_FULL, + BONE_UPDATE_ROTATION_ONLY, + BONE_UPDATE_MAX + }; + + void set_hand_tracker(const StringName &p_tracker_name); + StringName get_hand_tracker() const; + + void set_target(const NodePath &p_target); + NodePath get_target() const; + + void set_bone_update(BoneUpdate p_bone_update); + BoneUpdate get_bone_update() const; + + void _notification(int p_what); + +protected: + static void _bind_methods(); + +private: + struct JointData { + int bone = -1; + int parent_joint = -1; + }; + + StringName tracker_name = "/user/left"; + NodePath target; + BoneUpdate bone_update = BONE_UPDATE_FULL; + JointData joints[XRHandTracker::HAND_JOINT_MAX]; + + Skeleton3D *get_skeleton(); + void _get_joint_data(); + void _update_skeleton(); + void _tracker_changed(StringName p_tracker_name, const Ref &p_tracker); +}; + +VARIANT_ENUM_CAST(XRHandModifier3D::BoneUpdate) + +#endif // XR_HAND_MODIFIER_3D_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 18e96454ba3..653d7ee4dbc 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -274,6 +274,7 @@ #include "scene/3d/voxel_gi.h" #include "scene/3d/world_environment.h" #include "scene/3d/xr_face_modifier_3d.h" +#include "scene/3d/xr_hand_modifier_3d.h" #include "scene/3d/xr_nodes.h" #include "scene/animation/root_motion_view.h" #include "scene/resources/environment.h" @@ -523,6 +524,7 @@ void register_scene_types() { GDREGISTER_CLASS(XRController3D); GDREGISTER_CLASS(XRAnchor3D); GDREGISTER_CLASS(XROrigin3D); + GDREGISTER_CLASS(XRHandModifier3D); GDREGISTER_CLASS(XRFaceModifier3D); GDREGISTER_CLASS(MeshInstance3D); GDREGISTER_CLASS(OccluderInstance3D); diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 150c155f1ab..7a55219af21 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -87,6 +87,7 @@ #include "text/text_server_extension.h" #include "text_server.h" #include "xr/xr_face_tracker.h" +#include "xr/xr_hand_tracker.h" #include "xr/xr_interface.h" #include "xr/xr_interface_extension.h" #include "xr/xr_positional_tracker.h" @@ -193,6 +194,7 @@ void register_server_types() { GDREGISTER_ABSTRACT_CLASS(RenderingDevice); GDREGISTER_ABSTRACT_CLASS(XRInterface); + GDREGISTER_CLASS(XRHandTracker); GDREGISTER_CLASS(XRInterfaceExtension); // can't register this as virtual because we need a creation function for our extensions. GDREGISTER_CLASS(XRPose); GDREGISTER_CLASS(XRPositionalTracker); diff --git a/servers/xr/xr_hand_tracker.cpp b/servers/xr/xr_hand_tracker.cpp new file mode 100644 index 00000000000..8cc2d5f7d28 --- /dev/null +++ b/servers/xr/xr_hand_tracker.cpp @@ -0,0 +1,179 @@ +/**************************************************************************/ +/* xr_hand_tracker.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "xr_hand_tracker.h" + +void XRHandTracker::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_hand", "hand"), &XRHandTracker::set_hand); + ClassDB::bind_method(D_METHOD("get_hand"), &XRHandTracker::get_hand); + + ClassDB::bind_method(D_METHOD("set_has_tracking_data", "has_data"), &XRHandTracker::set_has_tracking_data); + ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRHandTracker::get_has_tracking_data); + + ClassDB::bind_method(D_METHOD("set_hand_tracking_source", "source"), &XRHandTracker::set_hand_tracking_source); + ClassDB::bind_method(D_METHOD("get_hand_tracking_source"), &XRHandTracker::get_hand_tracking_source); + + ClassDB::bind_method(D_METHOD("set_hand_joint_flags", "joint", "flags"), &XRHandTracker::set_hand_joint_flags); + ClassDB::bind_method(D_METHOD("get_hand_joint_flags", "joint"), &XRHandTracker::get_hand_joint_flags); + + ClassDB::bind_method(D_METHOD("set_hand_joint_transform", "joint", "transform"), &XRHandTracker::set_hand_joint_transform); + ClassDB::bind_method(D_METHOD("get_hand_joint_transform", "joint"), &XRHandTracker::get_hand_joint_transform); + + ClassDB::bind_method(D_METHOD("set_hand_joint_radius", "joint", "radius"), &XRHandTracker::set_hand_joint_radius); + ClassDB::bind_method(D_METHOD("get_hand_joint_radius", "joint"), &XRHandTracker::get_hand_joint_radius); + + ClassDB::bind_method(D_METHOD("set_hand_joint_linear_velocity", "joint", "linear_velocity"), &XRHandTracker::set_hand_joint_linear_velocity); + ClassDB::bind_method(D_METHOD("get_hand_joint_linear_velocity", "joint"), &XRHandTracker::get_hand_joint_linear_velocity); + + ClassDB::bind_method(D_METHOD("set_hand_joint_angular_velocity", "joint", "angular_velocity"), &XRHandTracker::set_hand_joint_angular_velocity); + ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "joint"), &XRHandTracker::get_hand_joint_angular_velocity); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "has_tracking_data", PROPERTY_HINT_NONE), "set_has_tracking_data", "get_has_tracking_data"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "hand_tracking_source", PROPERTY_HINT_ENUM, "Unknown,Unobstructed,Controller"), "set_hand_tracking_source", "get_hand_tracking_source"); + + BIND_ENUM_CONSTANT(HAND_LEFT); + BIND_ENUM_CONSTANT(HAND_RIGHT); + BIND_ENUM_CONSTANT(HAND_MAX); + + BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_UNKNOWN); + BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_UNOBSTRUCTED); + BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_CONTROLLER); + BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_MAX); + + BIND_ENUM_CONSTANT(HAND_JOINT_PALM); + BIND_ENUM_CONSTANT(HAND_JOINT_WRIST); + BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_METACARPAL); + BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_PHALANX_PROXIMAL); + BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_PHALANX_DISTAL); + BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_TIP); + BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_FINGER_METACARPAL); + BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_FINGER_PHALANX_PROXIMAL); + BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_FINGER_PHALANX_INTERMEDIATE); + BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_FINGER_PHALANX_DISTAL); + BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_FINGER_TIP); + BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_FINGER_METACARPAL); + BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_FINGER_PHALANX_PROXIMAL); + BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_FINGER_PHALANX_INTERMEDIATE); + BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_FINGER_PHALANX_DISTAL); + BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_FINGER_TIP); + BIND_ENUM_CONSTANT(HAND_JOINT_RING_FINGER_METACARPAL); + BIND_ENUM_CONSTANT(HAND_JOINT_RING_FINGER_PHALANX_PROXIMAL); + BIND_ENUM_CONSTANT(HAND_JOINT_RING_FINGER_PHALANX_INTERMEDIATE); + BIND_ENUM_CONSTANT(HAND_JOINT_RING_FINGER_PHALANX_DISTAL); + BIND_ENUM_CONSTANT(HAND_JOINT_RING_FINGER_TIP); + BIND_ENUM_CONSTANT(HAND_JOINT_PINKY_FINGER_METACARPAL); + BIND_ENUM_CONSTANT(HAND_JOINT_PINKY_FINGER_PHALANX_PROXIMAL); + BIND_ENUM_CONSTANT(HAND_JOINT_PINKY_FINGER_PHALANX_INTERMEDIATE); + BIND_ENUM_CONSTANT(HAND_JOINT_PINKY_FINGER_PHALANX_DISTAL); + BIND_ENUM_CONSTANT(HAND_JOINT_PINKY_FINGER_TIP); + BIND_ENUM_CONSTANT(HAND_JOINT_MAX); + + BIND_BITFIELD_FLAG(HAND_JOINT_FLAG_ORIENTATION_VALID); + BIND_BITFIELD_FLAG(HAND_JOINT_FLAG_ORIENTATION_TRACKED); + BIND_BITFIELD_FLAG(HAND_JOINT_FLAG_POSITION_VALID); + BIND_BITFIELD_FLAG(HAND_JOINT_FLAG_POSITION_TRACKED); + BIND_BITFIELD_FLAG(HAND_JOINT_FLAG_LINEAR_VELOCITY_VALID); + BIND_BITFIELD_FLAG(HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID); +} + +void XRHandTracker::set_hand(XRHandTracker::Hand p_hand) { + hand = p_hand; +} + +XRHandTracker::Hand XRHandTracker::get_hand() const { + return hand; +} + +void XRHandTracker::set_has_tracking_data(bool p_has_tracking_data) { + has_tracking_data = p_has_tracking_data; +} + +bool XRHandTracker::get_has_tracking_data() const { + return has_tracking_data; +} + +void XRHandTracker::set_hand_tracking_source(XRHandTracker::HandTrackingSource p_source) { + hand_tracking_source = p_source; +} + +XRHandTracker::HandTrackingSource XRHandTracker::get_hand_tracking_source() const { + return hand_tracking_source; +} + +void XRHandTracker::set_hand_joint_flags(XRHandTracker::HandJoint p_joint, BitField p_flags) { + ERR_FAIL_INDEX(p_joint, HAND_JOINT_MAX); + hand_joint_flags[p_joint] = p_flags; +} + +BitField XRHandTracker::get_hand_joint_flags(XRHandTracker::HandJoint p_joint) const { + ERR_FAIL_INDEX_V(p_joint, HAND_JOINT_MAX, BitField()); + return hand_joint_flags[p_joint]; +} + +void XRHandTracker::set_hand_joint_transform(XRHandTracker::HandJoint p_joint, const Transform3D &p_transform) { + ERR_FAIL_INDEX(p_joint, HAND_JOINT_MAX); + hand_joint_transforms[p_joint] = p_transform; +} + +Transform3D XRHandTracker::get_hand_joint_transform(XRHandTracker::HandJoint p_joint) const { + ERR_FAIL_INDEX_V(p_joint, HAND_JOINT_MAX, Transform3D()); + return hand_joint_transforms[p_joint]; +} + +void XRHandTracker::set_hand_joint_radius(XRHandTracker::HandJoint p_joint, float p_radius) { + ERR_FAIL_INDEX(p_joint, HAND_JOINT_MAX); + hand_joint_radii[p_joint] = p_radius; +} + +float XRHandTracker::get_hand_joint_radius(XRHandTracker::HandJoint p_joint) const { + ERR_FAIL_INDEX_V(p_joint, HAND_JOINT_MAX, 0.0); + return hand_joint_radii[p_joint]; +} + +void XRHandTracker::set_hand_joint_linear_velocity(XRHandTracker::HandJoint p_joint, const Vector3 &p_velocity) { + ERR_FAIL_INDEX(p_joint, HAND_JOINT_MAX); + hand_joint_linear_velocities[p_joint] = p_velocity; +} + +Vector3 XRHandTracker::get_hand_joint_linear_velocity(XRHandTracker::HandJoint p_joint) const { + ERR_FAIL_INDEX_V(p_joint, HAND_JOINT_MAX, Vector3()); + return hand_joint_linear_velocities[p_joint]; +} + +void XRHandTracker::set_hand_joint_angular_velocity(XRHandTracker::HandJoint p_joint, const Vector3 &p_velocity) { + ERR_FAIL_INDEX(p_joint, HAND_JOINT_MAX); + hand_joint_angular_velocities[p_joint] = p_velocity; +} + +Vector3 XRHandTracker::get_hand_joint_angular_velocity(XRHandTracker::HandJoint p_joint) const { + ERR_FAIL_INDEX_V(p_joint, HAND_JOINT_MAX, Vector3()); + return hand_joint_angular_velocities[p_joint]; +} diff --git a/servers/xr/xr_hand_tracker.h b/servers/xr/xr_hand_tracker.h new file mode 100644 index 00000000000..648f02d1f85 --- /dev/null +++ b/servers/xr/xr_hand_tracker.h @@ -0,0 +1,137 @@ +/**************************************************************************/ +/* xr_hand_tracker.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef XR_HAND_TRACKER_H +#define XR_HAND_TRACKER_H + +#include "core/object/ref_counted.h" + +class XRHandTracker : public RefCounted { + GDCLASS(XRHandTracker, RefCounted); + _THREAD_SAFE_CLASS_ + +public: + enum Hand { + HAND_LEFT, + HAND_RIGHT, + HAND_MAX, + }; + + enum HandTrackingSource { + HAND_TRACKING_SOURCE_UNKNOWN, + HAND_TRACKING_SOURCE_UNOBSTRUCTED, + HAND_TRACKING_SOURCE_CONTROLLER, + HAND_TRACKING_SOURCE_MAX + }; + + enum HandJoint { + HAND_JOINT_PALM, + HAND_JOINT_WRIST, + HAND_JOINT_THUMB_METACARPAL, + HAND_JOINT_THUMB_PHALANX_PROXIMAL, + HAND_JOINT_THUMB_PHALANX_DISTAL, + HAND_JOINT_THUMB_TIP, + HAND_JOINT_INDEX_FINGER_METACARPAL, + HAND_JOINT_INDEX_FINGER_PHALANX_PROXIMAL, + HAND_JOINT_INDEX_FINGER_PHALANX_INTERMEDIATE, + HAND_JOINT_INDEX_FINGER_PHALANX_DISTAL, + HAND_JOINT_INDEX_FINGER_TIP, + HAND_JOINT_MIDDLE_FINGER_METACARPAL, + HAND_JOINT_MIDDLE_FINGER_PHALANX_PROXIMAL, + HAND_JOINT_MIDDLE_FINGER_PHALANX_INTERMEDIATE, + HAND_JOINT_MIDDLE_FINGER_PHALANX_DISTAL, + HAND_JOINT_MIDDLE_FINGER_TIP, + HAND_JOINT_RING_FINGER_METACARPAL, + HAND_JOINT_RING_FINGER_PHALANX_PROXIMAL, + HAND_JOINT_RING_FINGER_PHALANX_INTERMEDIATE, + HAND_JOINT_RING_FINGER_PHALANX_DISTAL, + HAND_JOINT_RING_FINGER_TIP, + HAND_JOINT_PINKY_FINGER_METACARPAL, + HAND_JOINT_PINKY_FINGER_PHALANX_PROXIMAL, + HAND_JOINT_PINKY_FINGER_PHALANX_INTERMEDIATE, + HAND_JOINT_PINKY_FINGER_PHALANX_DISTAL, + HAND_JOINT_PINKY_FINGER_TIP, + HAND_JOINT_MAX, + }; + + enum HandJointFlags { + HAND_JOINT_FLAG_ORIENTATION_VALID = 1, + HAND_JOINT_FLAG_ORIENTATION_TRACKED = 2, + HAND_JOINT_FLAG_POSITION_VALID = 4, + HAND_JOINT_FLAG_POSITION_TRACKED = 8, + HAND_JOINT_FLAG_LINEAR_VELOCITY_VALID = 16, + HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID = 32, + }; + + void set_hand(Hand p_hand); + Hand get_hand() const; + + void set_has_tracking_data(bool p_has_tracking_data); + bool get_has_tracking_data() const; + + void set_hand_tracking_source(HandTrackingSource p_source); + HandTrackingSource get_hand_tracking_source() const; + + void set_hand_joint_flags(HandJoint p_joint, BitField p_flags); + BitField get_hand_joint_flags(HandJoint p_joint) const; + + void set_hand_joint_transform(HandJoint p_joint, const Transform3D &p_transform); + Transform3D get_hand_joint_transform(HandJoint p_joint) const; + + void set_hand_joint_radius(HandJoint p_joint, float p_radius); + float get_hand_joint_radius(HandJoint p_joint) const; + + void set_hand_joint_linear_velocity(HandJoint p_joint, const Vector3 &p_velocity); + Vector3 get_hand_joint_linear_velocity(HandJoint p_joint) const; + + void set_hand_joint_angular_velocity(HandJoint p_joint, const Vector3 &p_velocity); + Vector3 get_hand_joint_angular_velocity(HandJoint p_joint) const; + +protected: + static void _bind_methods(); + +private: + Hand hand = HAND_LEFT; + bool has_tracking_data = false; + HandTrackingSource hand_tracking_source = HAND_TRACKING_SOURCE_UNKNOWN; + + BitField hand_joint_flags[HAND_JOINT_MAX]; + Transform3D hand_joint_transforms[HAND_JOINT_MAX]; + float hand_joint_radii[HAND_JOINT_MAX] = {}; + Vector3 hand_joint_linear_velocities[HAND_JOINT_MAX]; + Vector3 hand_joint_angular_velocities[HAND_JOINT_MAX]; +}; + +VARIANT_ENUM_CAST(XRHandTracker::Hand) +VARIANT_ENUM_CAST(XRHandTracker::HandTrackingSource) +VARIANT_ENUM_CAST(XRHandTracker::HandJoint) +VARIANT_BITFIELD_CAST(XRHandTracker::HandJointFlags) + +#endif // XR_HAND_TRACKER_H diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index b3bb0a3702a..1e497e22c3b 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -31,6 +31,7 @@ #include "xr_server.h" #include "core/config/project_settings.h" #include "xr/xr_face_tracker.h" +#include "xr/xr_hand_tracker.h" #include "xr/xr_interface.h" #include "xr/xr_positional_tracker.h" @@ -75,6 +76,11 @@ void XRServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_trackers", "tracker_types"), &XRServer::get_trackers); ClassDB::bind_method(D_METHOD("get_tracker", "tracker_name"), &XRServer::get_tracker); + ClassDB::bind_method(D_METHOD("add_hand_tracker", "tracker_name", "hand_tracker"), &XRServer::add_hand_tracker); + ClassDB::bind_method(D_METHOD("remove_hand_tracker", "tracker_name"), &XRServer::remove_hand_tracker); + ClassDB::bind_method(D_METHOD("get_hand_trackers"), &XRServer::get_hand_trackers); + ClassDB::bind_method(D_METHOD("get_hand_tracker", "tracker_name"), &XRServer::get_hand_tracker); + ClassDB::bind_method(D_METHOD("add_face_tracker", "tracker_name", "face_tracker"), &XRServer::add_face_tracker); ClassDB::bind_method(D_METHOD("remove_face_tracker", "tracker_name"), &XRServer::remove_face_tracker); ClassDB::bind_method(D_METHOD("get_face_trackers"), &XRServer::get_face_trackers); @@ -104,6 +110,10 @@ void XRServer::_bind_methods() { ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); + ADD_SIGNAL(MethodInfo("hand_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "hand_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRHandTracker"))); + ADD_SIGNAL(MethodInfo("hand_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "hand_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRHandTracker"))); + ADD_SIGNAL(MethodInfo("hand_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"))); + ADD_SIGNAL(MethodInfo("face_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker"))); ADD_SIGNAL(MethodInfo("face_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker"))); ADD_SIGNAL(MethodInfo("face_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"))); @@ -362,6 +372,44 @@ PackedStringArray XRServer::get_suggested_pose_names(const StringName &p_tracker return arr; } +void XRServer::add_hand_tracker(const StringName &p_tracker_name, Ref p_hand_tracker) { + ERR_FAIL_COND(p_hand_tracker.is_null()); + + if (!hand_trackers.has(p_tracker_name)) { + // We don't have a tracker with this name, we're going to add it. + hand_trackers[p_tracker_name] = p_hand_tracker; + emit_signal(SNAME("hand_tracker_added"), p_tracker_name, p_hand_tracker); + } else if (hand_trackers[p_tracker_name] != p_hand_tracker) { + // We already have a tracker with this name, we're going to replace it. + hand_trackers[p_tracker_name] = p_hand_tracker; + emit_signal(SNAME("hand_tracker_updated"), p_tracker_name, p_hand_tracker); + } +} + +void XRServer::remove_hand_tracker(const StringName &p_tracker_name) { + // Skip if no hand tracker is found. + if (!hand_trackers.has(p_tracker_name)) { + return; + } + + // Send the removed signal, then remove the hand tracker. + emit_signal(SNAME("hand_tracker_removed"), p_tracker_name); + hand_trackers.erase(p_tracker_name); +} + +Dictionary XRServer::get_hand_trackers() const { + return hand_trackers; +} + +Ref XRServer::get_hand_tracker(const StringName &p_tracker_name) const { + // Skip if no tracker is found. + if (!hand_trackers.has(p_tracker_name)) { + return Ref(); + } + + return hand_trackers[p_tracker_name]; +} + void XRServer::add_face_tracker(const StringName &p_tracker_name, Ref p_face_tracker) { ERR_FAIL_COND(p_face_tracker.is_null()); diff --git a/servers/xr_server.h b/servers/xr_server.h index 0a4e020a1f6..3e45bbb76c9 100644 --- a/servers/xr_server.h +++ b/servers/xr_server.h @@ -39,6 +39,7 @@ class XRInterface; class XRPositionalTracker; +class XRHandTracker; class XRFaceTracker; /** @@ -86,7 +87,7 @@ private: Vector> interfaces; Dictionary trackers; - + Dictionary hand_trackers; Dictionary face_trackers; Ref primary_interface; /* we'll identify one interface as primary, this will be used by our viewports */ @@ -186,6 +187,14 @@ public: PackedStringArray get_suggested_pose_names(const StringName &p_tracker_name) const; // Q: Should we add get_suggested_input_names and get_suggested_haptic_names even though we don't use them for the IDE? + /* + Hand trackers are objects that expose the tracked joints of a hand. + */ + void add_hand_tracker(const StringName &p_tracker_name, Ref p_hand_tracker); + void remove_hand_tracker(const StringName &p_tracker_name); + Dictionary get_hand_trackers() const; + Ref get_hand_tracker(const StringName &p_tracker_name) const; + /* Face trackers are objects that expose the tracked blend shapes of a face. */