diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1d65f6fb9ab..a46f1584e26 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2947,10 +2947,19 @@
Specify whether to enable eye tracking for this project. Depending on the platform, additional export configuration may be needed.
- If true the hand interaction profile extension will be activated if supported by the platform.
+ If [code]true[/code] the hand interaction profile extension will be activated if supported by the platform.
-
- If true we enable the hand tracking extension if available.
+
+ If [code]true[/code], the hand tracking extension is enabled if available.
+ [b]Note:[/b] By default hand tracking will only work for data sources chosen by the XR runtime. For SteamVR this is the controller inferred data source, for most other runtimes this is the unobstructed data source. There is no way to query this. If a runtime supports the OpenXR data source extension you can use the [member xr/openxr/extensions/hand_tracking_controller_data_source] and/or [member xr/openxr/extensions/hand_tracking_unobstructed_data_source] to indicate you wish to enable these data sources. If neither is selected the data source extension is not enabled and the XR runtimes default behavior persists.
+
+
+ If [code]true[/code], support for the controller inferred data source is requested. If supported, you will receive hand tracking data even if the user has a controller in hand, with finger positions automatically inferred from controller input and/or sensors.
+ [b]Node:[/b] This requires the OpenXR data source extension and controller inferred handtracking to be supported by the XR runtime. If not supported this setting will be ignored. [member xr/openxr/extensions/hand_tracking] must be enabled for this setting to be used.
+
+
+ If [code]true[/code], support for the unobstructed data source is requested. If supported, you will receive hand tracking data based on the actual finger positions of the user often determined by optical tracking.
+ [b]Node:[/b] This requires the OpenXR data source extension and unobstructed handtracking to be supported by the XR runtime. If not supported this setting will be ignored. [member xr/openxr/extensions/hand_tracking] must be enabled for this setting to be used.
Specify whether OpenXR should be configured for an HMD or a hand held device.
diff --git a/doc/classes/XRHandTracker.xml b/doc/classes/XRHandTracker.xml
index 636af6625bd..79ea237480a 100644
--- a/doc/classes/XRHandTracker.xml
+++ b/doc/classes/XRHandTracker.xml
@@ -107,7 +107,10 @@
The source of hand tracking data is a controller, meaning that joint positions are inferred from controller inputs.
-
+
+ No hand tracking data is tracked, this either means the hand is obscured, the controller is turned off, or tracking is not supported for the current input type.
+
+
Represents the size of the [enum HandTrackingSource] enum.
diff --git a/main/main.cpp b/main/main.cpp
index bdae1bb1b07..9ee88af60e1 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2512,7 +2512,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_BASIC("xr/openxr/startup_alert", true);
// OpenXR project extensions settings.
- GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", true);
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", false);
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_unobstructed_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_controller_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT
GLOBAL_DEF_RST_BASIC("xr/openxr/extensions/hand_interaction_profile", false);
GLOBAL_DEF_BASIC("xr/openxr/extensions/eye_gaze_interaction", false);
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index d9a66aa827d..6d6231f6fa2 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -58,9 +58,14 @@ OpenXRHandTrackingExtension::~OpenXRHandTrackingExtension() {
HashMap OpenXRHandTrackingExtension::get_requested_extensions() {
HashMap request_extensions;
+ unobstructed_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_unobstructed_data_source");
+ controller_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_controller_data_source");
+
request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext;
request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext;
- request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext;
+ if (unobstructed_data_source || controller_data_source) {
+ request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext;
+ }
return request_extensions;
}
@@ -141,10 +146,18 @@ void OpenXRHandTrackingExtension::on_process() {
void *next_pointer = nullptr;
// Originally not all XR runtimes supported hand tracking data sourced both from controllers and normal hand tracking.
- // With this extension we can indicate we accept input from both sources so hand tracking data is consistently provided
- // on runtimes that support this.
- XrHandTrackingDataSourceEXT data_sources[2] = { XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT, XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT };
- XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, 2, data_sources };
+ // With this extension we can indicate we wish to accept input from either or both sources.
+ // This functionality is subject to the abilities of the XR runtime and requires the data source extension.
+ // Note: If the data source extension is not available, no guarantees can be made on what the XR runtime supports.
+ uint32_t data_source_count = 0;
+ XrHandTrackingDataSourceEXT data_sources[2];
+ if (unobstructed_data_source) {
+ data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT;
+ }
+ if (controller_data_source) {
+ data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT;
+ }
+ XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, data_source_count, data_sources };
if (hand_tracking_source_ext) {
// If supported include this info
next_pointer = &data_source_info;
@@ -224,7 +237,9 @@ 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_hand_tracking_source(XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
+ godot_tracker->invalidate_pose("default");
continue;
}
@@ -235,8 +250,6 @@ void OpenXRHandTrackingExtension::on_process() {
}
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)
@@ -245,7 +258,6 @@ void OpenXRHandTrackingExtension::on_process() {
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;
@@ -285,23 +297,35 @@ void OpenXRHandTrackingExtension::on_process() {
godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius);
if (joint == XR_HAND_JOINT_PALM_EXT) {
- 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);
if (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) {
+ XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source;
+
+ XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
+ if (hand_tracking_source_ext) {
+ if (!data_source.isActive) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED;
+ } else 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;
+ } else {
+ // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them.
+ WARN_PRINT_ONCE("Unknown active data source found!");
+ source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
+ }
+ }
+ godot_tracker->set_hand_tracking_source(source);
+ godot_tracker->set_has_tracking_data(true);
godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity);
} else {
+ godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
godot_tracker->invalidate_pose("default");
}
}
}
} else {
+ godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
godot_tracker->invalidate_pose("default");
}
@@ -349,16 +373,17 @@ XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTra
OpenXRHandTrackingExtension::HandTrackedSource OpenXRHandTrackingExtension::get_hand_tracking_source(HandTrackedHands p_hand) const {
ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, OPENXR_SOURCE_UNKNOWN);
- if (hand_tracking_source_ext && hand_trackers[p_hand].data_source.isActive) {
- switch (hand_trackers[p_hand].data_source.dataSource) {
- case XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT:
- return OPENXR_SOURCE_UNOBSTRUCTED;
-
- case XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT:
- return OPENXR_SOURCE_CONTROLLER;
-
- default:
- return OPENXR_SOURCE_UNKNOWN;
+ if (hand_tracking_source_ext) {
+ if (!hand_trackers[p_hand].data_source.isActive) {
+ return OPENXR_SOURCE_NOT_TRACKED;
+ } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
+ return OPENXR_SOURCE_UNOBSTRUCTED;
+ } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
+ return OPENXR_SOURCE_CONTROLLER;
+ } else {
+ // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them.
+ WARN_PRINT_ONCE("Unknown active data source found!");
+ return OPENXR_SOURCE_UNKNOWN;
}
}
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h
index f709bc05c21..2c34ff7f21a 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.h
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h
@@ -48,6 +48,7 @@ public:
OPENXR_SOURCE_UNKNOWN,
OPENXR_SOURCE_UNOBSTRUCTED,
OPENXR_SOURCE_CONTROLLER,
+ OPENXR_SOURCE_NOT_TRACKED,
OPENXR_SOURCE_MAX
};
@@ -110,6 +111,8 @@ private:
bool hand_tracking_ext = false;
bool hand_motion_range_ext = false;
bool hand_tracking_source_ext = false;
+ bool unobstructed_data_source = false;
+ bool controller_data_source = false;
// functions
void cleanup_hand_tracking();
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index 3f4b9626417..b71f9bc0c49 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -303,6 +303,8 @@ StringName XRNode3D::get_pose_name() const {
void XRNode3D::set_show_when_tracked(bool p_show) {
show_when_tracked = p_show;
+
+ _update_visibility();
}
bool XRNode3D::get_show_when_tracked() const {
@@ -361,6 +363,9 @@ void XRNode3D::_bind_tracker() {
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
_set_has_tracking_data(pose->get_has_tracking_data());
+ } else {
+ // Pose has been invalidated or was never set.
+ _set_has_tracking_data(false);
}
}
}
@@ -407,6 +412,10 @@ void XRNode3D::_pose_lost_tracking(const Ref &p_pose) {
}
void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
+ // Always update our visibility, we may have set our tracking data
+ // when conditions weren't right.
+ _update_visibility();
+
// Ignore if the has_tracking_data state isn't changing.
if (p_has_tracking_data == has_tracking_data) {
return;
@@ -415,10 +424,19 @@ void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
// Handle change of has_tracking_data.
has_tracking_data = p_has_tracking_data;
emit_signal(SNAME("tracking_changed"), has_tracking_data);
+}
+void XRNode3D::_update_visibility() {
// If configured, show or hide the node based on tracking data.
if (show_when_tracked) {
- set_visible(has_tracking_data);
+ // Only react to this if we have a primary interface.
+ XRServer *xr_server = XRServer::get_singleton();
+ if (xr_server != nullptr) {
+ Ref xr_interface = xr_server->get_primary_interface();
+ if (xr_interface.is_valid()) {
+ set_visible(has_tracking_data);
+ }
+ }
}
}
diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h
index a42f6d470ff..94c3923433f 100644
--- a/scene/3d/xr_nodes.h
+++ b/scene/3d/xr_nodes.h
@@ -95,6 +95,8 @@ protected:
void _pose_lost_tracking(const Ref &p_pose);
void _set_has_tracking_data(bool p_has_tracking_data);
+ void _update_visibility();
+
public:
void _validate_property(PropertyInfo &p_property) const;
void set_tracker(const StringName &p_tracker_name);
diff --git a/servers/xr/xr_hand_tracker.cpp b/servers/xr/xr_hand_tracker.cpp
index abfe2e98675..2f64737ed62 100644
--- a/servers/xr/xr_hand_tracker.cpp
+++ b/servers/xr/xr_hand_tracker.cpp
@@ -60,6 +60,7 @@ void XRHandTracker::_bind_methods() {
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_NOT_TRACKED);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_MAX);
BIND_ENUM_CONSTANT(HAND_JOINT_PALM);
diff --git a/servers/xr/xr_hand_tracker.h b/servers/xr/xr_hand_tracker.h
index c7c18a31f8a..7837df3e0a7 100644
--- a/servers/xr/xr_hand_tracker.h
+++ b/servers/xr/xr_hand_tracker.h
@@ -42,6 +42,7 @@ public:
HAND_TRACKING_SOURCE_UNKNOWN,
HAND_TRACKING_SOURCE_UNOBSTRUCTED,
HAND_TRACKING_SOURCE_CONTROLLER,
+ HAND_TRACKING_SOURCE_NOT_TRACKED,
HAND_TRACKING_SOURCE_MAX
};