Browse Source

OpenXR - add access to hand joint validity flags

Bastiaan Olij 1 year ago
parent
commit
50693a5420

+ 36 - 0
modules/openxr/doc_classes/OpenXRInterface.xml

@@ -31,6 +31,14 @@
 				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]!
 				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]!
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_hand_joint_flags" qualifiers="const">
+			<return type="int" enum="OpenXRInterface.HandJointFlags" is_bitfield="true" />
+			<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
+			<param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
+			<description>
+				If handtracking is enabled, returns flags that inform us of the validity of the tracking data.
+			</description>
+		</method>
 		<method name="get_hand_joint_linear_velocity" qualifiers="const">
 		<method name="get_hand_joint_linear_velocity" qualifiers="const">
 			<return type="Vector3" />
 			<return type="Vector3" />
 			<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
 			<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
@@ -91,6 +99,13 @@
 				[b]Note:[/b] This feature is only available on the compatibility renderer and currently only available on some stand alone headsets. For Vulkan set [member Viewport.vrs_mode] to [code]VRS_XR[/code] on desktop.
 				[b]Note:[/b] This feature is only available on the compatibility renderer and currently only available on some stand alone headsets. For Vulkan set [member Viewport.vrs_mode] to [code]VRS_XR[/code] on desktop.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="is_hand_tracking_supported">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if OpenXRs hand tracking is supported and enabled.
+				[b]Note:[/b] This only returns a valid value after OpenXR has been initialized.
+			</description>
+		</method>
 		<method name="set_action_set_active">
 		<method name="set_action_set_active">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="name" type="String" />
 			<param index="0" name="name" type="String" />
@@ -246,5 +261,26 @@
 		<constant name="HAND_JOINT_MAX" value="26" enum="HandJoints">
 		<constant name="HAND_JOINT_MAX" value="26" enum="HandJoints">
 			Maximum value for the hand joint enum.
 			Maximum value for the hand joint enum.
 		</constant>
 		</constant>
+		<constant name="HAND_JOINT_NONE" value="0" enum="HandJointFlags" is_bitfield="true">
+			No flags are set.
+		</constant>
+		<constant name="HAND_JOINT_ORIENTATION_VALID" value="1" enum="HandJointFlags" is_bitfield="true">
+			If set, the orientation data is valid, otherwise, the orientation data is unreliable and should not be used.
+		</constant>
+		<constant name="HAND_JOINT_ORIENTATION_TRACKED" value="2" enum="HandJointFlags" is_bitfield="true">
+			If set, the orientation data comes from tracking data, otherwise, the orientation data contains predicted data.
+		</constant>
+		<constant name="HAND_JOINT_POSITION_VALID" value="4" enum="HandJointFlags" is_bitfield="true">
+			If set, the positional data is valid, otherwise, the positional data is unreliable and should not be used.
+		</constant>
+		<constant name="HAND_JOINT_POSITION_TRACKED" value="8" enum="HandJointFlags" is_bitfield="true">
+			If set, the positional data comes from tracking data, otherwise, the positional data contains predicted data.
+		</constant>
+		<constant name="HAND_JOINT_LINEAR_VELOCITY_VALID" value="16" enum="HandJointFlags" is_bitfield="true">
+			If set, our linear velocity data is valid, otherwise, the linear velocity data is unreliable and should not be used.
+		</constant>
+		<constant name="HAND_JOINT_ANGULAR_VELOCITY_VALID" value="32" enum="HandJointFlags" is_bitfield="true">
+			If set, our angular velocity data is valid, otherwise, the angular velocity data is unreliable and should not be used.
+		</constant>
 	</constants>
 	</constants>
 </class>
 </class>

+ 24 - 0
modules/openxr/extensions/openxr_hand_tracking_extension.cpp

@@ -245,6 +245,18 @@ void OpenXRHandTrackingExtension::set_motion_range(HandTrackedHands p_hand, XrHa
 	hand_trackers[p_hand].motion_range = p_motion_range;
 	hand_trackers[p_hand].motion_range = p_motion_range;
 }
 }
 
 
+XrSpaceLocationFlags OpenXRHandTrackingExtension::get_hand_joint_location_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+	ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, XrSpaceLocationFlags(0));
+	ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, XrSpaceLocationFlags(0));
+
+	if (!hand_trackers[p_hand].is_initialized) {
+		return XrSpaceLocationFlags(0);
+	}
+
+	const XrHandJointLocationEXT &location = hand_trackers[p_hand].joint_locations[p_joint];
+	return location.locationFlags;
+}
+
 Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
 Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
 	ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Quaternion());
 	ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Quaternion());
 	ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Quaternion());
 	ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Quaternion());
@@ -280,6 +292,18 @@ float OpenXRHandTrackingExtension::get_hand_joint_radius(HandTrackedHands p_hand
 	return hand_trackers[p_hand].joint_locations[p_joint].radius;
 	return hand_trackers[p_hand].joint_locations[p_joint].radius;
 }
 }
 
 
+XrSpaceVelocityFlags OpenXRHandTrackingExtension::get_hand_joint_velocity_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+	ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, XrSpaceVelocityFlags(0));
+	ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, XrSpaceVelocityFlags(0));
+
+	if (!hand_trackers[p_hand].is_initialized) {
+		return XrSpaceVelocityFlags(0);
+	}
+
+	const XrHandJointVelocityEXT &velocity = hand_trackers[p_hand].joint_velocities[p_joint];
+	return velocity.velocityFlags;
+}
+
 Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
 Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
 	ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3());
 	ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3());
 	ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
 	ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());

+ 2 - 0
modules/openxr/extensions/openxr_hand_tracking_extension.h

@@ -77,10 +77,12 @@ public:
 	XrHandJointsMotionRangeEXT get_motion_range(HandTrackedHands p_hand) const;
 	XrHandJointsMotionRangeEXT get_motion_range(HandTrackedHands p_hand) const;
 	void set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range);
 	void set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range);
 
 
+	XrSpaceLocationFlags get_hand_joint_location_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	Quaternion get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	Quaternion get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	Vector3 get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	Vector3 get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	float get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	float get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 
 
+	XrSpaceVelocityFlags get_hand_joint_velocity_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	Vector3 get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	Vector3 get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	Vector3 get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 	Vector3 get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
 
 

+ 89 - 8
modules/openxr/openxr_interface.cpp

@@ -77,6 +77,8 @@ void OpenXRInterface::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_motion_range", "hand", "motion_range"), &OpenXRInterface::set_motion_range);
 	ClassDB::bind_method(D_METHOD("set_motion_range", "hand", "motion_range"), &OpenXRInterface::set_motion_range);
 	ClassDB::bind_method(D_METHOD("get_motion_range", "hand"), &OpenXRInterface::get_motion_range);
 	ClassDB::bind_method(D_METHOD("get_motion_range", "hand"), &OpenXRInterface::get_motion_range);
 
 
+	ClassDB::bind_method(D_METHOD("get_hand_joint_flags", "hand", "joint"), &OpenXRInterface::get_hand_joint_flags);
+
 	ClassDB::bind_method(D_METHOD("get_hand_joint_rotation", "hand", "joint"), &OpenXRInterface::get_hand_joint_rotation);
 	ClassDB::bind_method(D_METHOD("get_hand_joint_rotation", "hand", "joint"), &OpenXRInterface::get_hand_joint_rotation);
 	ClassDB::bind_method(D_METHOD("get_hand_joint_position", "hand", "joint"), &OpenXRInterface::get_hand_joint_position);
 	ClassDB::bind_method(D_METHOD("get_hand_joint_position", "hand", "joint"), &OpenXRInterface::get_hand_joint_position);
 	ClassDB::bind_method(D_METHOD("get_hand_joint_radius", "hand", "joint"), &OpenXRInterface::get_hand_joint_radius);
 	ClassDB::bind_method(D_METHOD("get_hand_joint_radius", "hand", "joint"), &OpenXRInterface::get_hand_joint_radius);
@@ -84,6 +86,9 @@ void OpenXRInterface::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_hand_joint_linear_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_linear_velocity);
 	ClassDB::bind_method(D_METHOD("get_hand_joint_linear_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_linear_velocity);
 	ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_angular_velocity);
 	ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_angular_velocity);
 
 
+	ClassDB::bind_method(D_METHOD("is_hand_tracking_supported"), &OpenXRInterface::is_hand_tracking_supported);
+	ClassDB::bind_method(D_METHOD("is_eye_gaze_interaction_supported"), &OpenXRInterface::is_eye_gaze_interaction_supported);
+
 	BIND_ENUM_CONSTANT(HAND_LEFT);
 	BIND_ENUM_CONSTANT(HAND_LEFT);
 	BIND_ENUM_CONSTANT(HAND_RIGHT);
 	BIND_ENUM_CONSTANT(HAND_RIGHT);
 	BIND_ENUM_CONSTANT(HAND_MAX);
 	BIND_ENUM_CONSTANT(HAND_MAX);
@@ -120,7 +125,13 @@ void OpenXRInterface::_bind_methods() {
 	BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_TIP);
 	BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_TIP);
 	BIND_ENUM_CONSTANT(HAND_JOINT_MAX);
 	BIND_ENUM_CONSTANT(HAND_JOINT_MAX);
 
 
-	ClassDB::bind_method(D_METHOD("is_eye_gaze_interaction_supported"), &OpenXRInterface::is_eye_gaze_interaction_supported);
+	BIND_BITFIELD_FLAG(HAND_JOINT_NONE);
+	BIND_BITFIELD_FLAG(HAND_JOINT_ORIENTATION_VALID);
+	BIND_BITFIELD_FLAG(HAND_JOINT_ORIENTATION_TRACKED);
+	BIND_BITFIELD_FLAG(HAND_JOINT_POSITION_VALID);
+	BIND_BITFIELD_FLAG(HAND_JOINT_POSITION_TRACKED);
+	BIND_BITFIELD_FLAG(HAND_JOINT_LINEAR_VELOCITY_VALID);
+	BIND_BITFIELD_FLAG(HAND_JOINT_ANGULAR_VELOCITY_VALID);
 }
 }
 
 
 StringName OpenXRInterface::get_name() const {
 StringName OpenXRInterface::get_name() const {
@@ -709,6 +720,21 @@ Array OpenXRInterface::get_available_display_refresh_rates() const {
 	}
 	}
 }
 }
 
 
+bool OpenXRInterface::is_hand_tracking_supported() {
+	if (openxr_api == nullptr) {
+		return false;
+	} else if (!openxr_api->is_initialized()) {
+		return false;
+	} else {
+		OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+		if (hand_tracking_ext == nullptr) {
+			return false;
+		} else {
+			return hand_tracking_ext->get_active();
+		}
+	}
+}
+
 bool OpenXRInterface::is_eye_gaze_interaction_supported() {
 bool OpenXRInterface::is_eye_gaze_interaction_supported() {
 	if (openxr_api == nullptr) {
 	if (openxr_api == nullptr) {
 		return false;
 		return false;
@@ -912,15 +938,39 @@ void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrack
 	if (hand_tracking_ext && hand_tracking_ext->get_active()) {
 	if (hand_tracking_ext && hand_tracking_ext->get_active()) {
 		OpenXRInterface::Tracker *tracker = find_tracker(p_path);
 		OpenXRInterface::Tracker *tracker = find_tracker(p_path);
 		if (tracker && tracker->positional_tracker.is_valid()) {
 		if (tracker && tracker->positional_tracker.is_valid()) {
-			// TODO add in confidence! Requires PR #82715
+			XrSpaceLocationFlags location_flags = hand_tracking_ext->get_hand_joint_location_flags(p_hand, XR_HAND_JOINT_PALM_EXT);
 
 
-			Transform3D transform;
-			transform.basis = Basis(hand_tracking_ext->get_hand_joint_rotation(p_hand, XR_HAND_JOINT_PALM_EXT));
-			transform.origin = hand_tracking_ext->get_hand_joint_position(p_hand, XR_HAND_JOINT_PALM_EXT);
-			Vector3 linear_velocity = hand_tracking_ext->get_hand_joint_linear_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
-			Vector3 angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
+			if (location_flags & (XR_SPACE_LOCATION_ORIENTATION_VALID_BIT + XR_SPACE_LOCATION_POSITION_VALID_BIT)) {
+				static const XrSpaceLocationFlags all_location_flags = XR_SPACE_LOCATION_ORIENTATION_VALID_BIT + XR_SPACE_LOCATION_POSITION_VALID_BIT + XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT + XR_SPACE_LOCATION_POSITION_TRACKED_BIT;
+				XRPose::TrackingConfidence confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW;
+				Transform3D transform;
+				Vector3 linear_velocity;
+				Vector3 angular_velocity;
+
+				if ((location_flags & all_location_flags) == all_location_flags) {
+					// All flags set? confidence is high!
+					confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH;
+				}
 
 
-			tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, XRPose::XR_TRACKING_CONFIDENCE_HIGH);
+				if (location_flags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) {
+					transform.basis = Basis(hand_tracking_ext->get_hand_joint_rotation(p_hand, XR_HAND_JOINT_PALM_EXT));
+				}
+				if (location_flags & XR_SPACE_LOCATION_POSITION_VALID_BIT) {
+					transform.origin = hand_tracking_ext->get_hand_joint_position(p_hand, XR_HAND_JOINT_PALM_EXT);
+				}
+
+				XrSpaceVelocityFlags velocity_flags = hand_tracking_ext->get_hand_joint_location_flags(p_hand, XR_HAND_JOINT_PALM_EXT);
+				if (velocity_flags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) {
+					linear_velocity = hand_tracking_ext->get_hand_joint_linear_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
+				}
+				if (velocity_flags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) {
+					angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
+				}
+
+				tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, confidence);
+			} else {
+				tracker->positional_tracker->invalidate_pose("skeleton");
+			}
 		}
 		}
 	}
 	}
 }
 }
@@ -1183,6 +1233,37 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_
 	return HAND_MOTION_RANGE_MAX;
 	return HAND_MOTION_RANGE_MAX;
 }
 }
 
 
+BitField<OpenXRInterface::HandJointFlags> OpenXRInterface::get_hand_joint_flags(Hand p_hand, HandJoints p_joint) const {
+	BitField<OpenXRInterface::HandJointFlags> bits;
+
+	OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+	if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+		XrSpaceLocationFlags location_flags = hand_tracking_ext->get_hand_joint_location_flags(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
+		if (location_flags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) {
+			bits.set_flag(HAND_JOINT_ORIENTATION_VALID);
+		}
+		if (location_flags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) {
+			bits.set_flag(HAND_JOINT_ORIENTATION_TRACKED);
+		}
+		if (location_flags & XR_SPACE_LOCATION_POSITION_VALID_BIT) {
+			bits.set_flag(HAND_JOINT_POSITION_VALID);
+		}
+		if (location_flags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) {
+			bits.set_flag(HAND_JOINT_POSITION_TRACKED);
+		}
+
+		XrSpaceVelocityFlags velocity_flags = hand_tracking_ext->get_hand_joint_velocity_flags(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
+		if (velocity_flags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) {
+			bits.set_flag(HAND_JOINT_LINEAR_VELOCITY_VALID);
+		}
+		if (velocity_flags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) {
+			bits.set_flag(HAND_JOINT_ANGULAR_VELOCITY_VALID);
+		}
+	}
+
+	return bits;
+}
+
 Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const {
 Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const {
 	OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
 	OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
 	if (hand_tracking_ext && hand_tracking_ext->get_active()) {
 	if (hand_tracking_ext && hand_tracking_ext->get_active()) {

+ 13 - 0
modules/openxr/openxr_interface.h

@@ -111,6 +111,7 @@ public:
 	virtual PackedStringArray get_suggested_tracker_names() const override;
 	virtual PackedStringArray get_suggested_tracker_names() const override;
 	virtual TrackingStatus get_tracking_status() const override;
 	virtual TrackingStatus get_tracking_status() const override;
 
 
+	bool is_hand_tracking_supported();
 	bool is_eye_gaze_interaction_supported();
 	bool is_eye_gaze_interaction_supported();
 
 
 	bool initialize_on_startup() const;
 	bool initialize_on_startup() const;
@@ -222,6 +223,17 @@ public:
 		HAND_JOINT_MAX = 26,
 		HAND_JOINT_MAX = 26,
 	};
 	};
 
 
+	enum HandJointFlags {
+		HAND_JOINT_NONE = 0,
+		HAND_JOINT_ORIENTATION_VALID = 1,
+		HAND_JOINT_ORIENTATION_TRACKED = 2,
+		HAND_JOINT_POSITION_VALID = 4,
+		HAND_JOINT_POSITION_TRACKED = 8,
+		HAND_JOINT_LINEAR_VELOCITY_VALID = 16,
+		HAND_JOINT_ANGULAR_VELOCITY_VALID = 32,
+	};
+
+	BitField<HandJointFlags> get_hand_joint_flags(Hand p_hand, HandJoints p_joint) const;
 	Quaternion get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const;
 	Quaternion get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const;
 	Vector3 get_hand_joint_position(Hand p_hand, HandJoints p_joint) const;
 	Vector3 get_hand_joint_position(Hand p_hand, HandJoints p_joint) const;
 	float get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const;
 	float get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const;
@@ -236,5 +248,6 @@ public:
 VARIANT_ENUM_CAST(OpenXRInterface::Hand)
 VARIANT_ENUM_CAST(OpenXRInterface::Hand)
 VARIANT_ENUM_CAST(OpenXRInterface::HandMotionRange)
 VARIANT_ENUM_CAST(OpenXRInterface::HandMotionRange)
 VARIANT_ENUM_CAST(OpenXRInterface::HandJoints)
 VARIANT_ENUM_CAST(OpenXRInterface::HandJoints)
+VARIANT_BITFIELD_CAST(OpenXRInterface::HandJointFlags)
 
 
 #endif // OPENXR_INTERFACE_H
 #endif // OPENXR_INTERFACE_H