initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

6
scene/3d/xr/SCsub Normal file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")

View File

@@ -0,0 +1,363 @@
/**************************************************************************/
/* xr_body_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_body_modifier_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "servers/xr_server.h"
void XRBodyModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_body_tracker", "tracker_name"), &XRBodyModifier3D::set_body_tracker);
ClassDB::bind_method(D_METHOD("get_body_tracker"), &XRBodyModifier3D::get_body_tracker);
ClassDB::bind_method(D_METHOD("set_body_update", "body_update"), &XRBodyModifier3D::set_body_update);
ClassDB::bind_method(D_METHOD("get_body_update"), &XRBodyModifier3D::get_body_update);
ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRBodyModifier3D::set_bone_update);
ClassDB::bind_method(D_METHOD("get_bone_update"), &XRBodyModifier3D::get_bone_update);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body_tracker"), "set_body_tracker", "get_body_tracker");
ADD_PROPERTY(PropertyInfo(Variant::INT, "body_update", PROPERTY_HINT_FLAGS, "Upper Body,Lower Body,Hands"), "set_body_update", "get_body_update");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update");
BIND_BITFIELD_FLAG(BODY_UPDATE_UPPER_BODY);
BIND_BITFIELD_FLAG(BODY_UPDATE_LOWER_BODY);
BIND_BITFIELD_FLAG(BODY_UPDATE_HANDS);
BIND_ENUM_CONSTANT(BONE_UPDATE_FULL);
BIND_ENUM_CONSTANT(BONE_UPDATE_ROTATION_ONLY);
BIND_ENUM_CONSTANT(BONE_UPDATE_MAX);
}
void XRBodyModifier3D::set_body_tracker(const StringName &p_tracker_name) {
tracker_name = p_tracker_name;
}
StringName XRBodyModifier3D::get_body_tracker() const {
return tracker_name;
}
void XRBodyModifier3D::set_body_update(BitField<BodyUpdate> p_body_update) {
body_update = p_body_update;
}
BitField<XRBodyModifier3D::BodyUpdate> XRBodyModifier3D::get_body_update() const {
return body_update;
}
void XRBodyModifier3D::set_bone_update(BoneUpdate p_bone_update) {
ERR_FAIL_INDEX(p_bone_update, BONE_UPDATE_MAX);
bone_update = p_bone_update;
}
XRBodyModifier3D::BoneUpdate XRBodyModifier3D::get_bone_update() const {
return bone_update;
}
void XRBodyModifier3D::_get_joint_data() {
// Table of Godot Humanoid bone names with some additions.
static const String bone_names[XRBodyTracker::JOINT_MAX] = {
"Root", // XRBodyTracker::JOINT_ROOT
// Upper Body Joints.
"Hips", // XRBodyTracker::JOINT_HIPS
"Spine", // XRBodyTracker::JOINT_SPINE
"Chest", // XRBodyTracker::JOINT_CHEST
"UpperChest", // XRBodyTracker::JOINT_UPPER_CHEST
"Neck", // XRBodyTracker::JOINT_NECK"
"Head", // XRBodyTracker::JOINT_HEAD"
"HeadTip", // XRBodyTracker::JOINT_HEAD_TIP"
"LeftShoulder", // XRBodyTracker::JOINT_LEFT_SHOULDER"
"LeftUpperArm", // XRBodyTracker::JOINT_LEFT_UPPER_ARM"
"LeftLowerArm", // XRBodyTracker::JOINT_LEFT_LOWER_ARM"
"RightShoulder", // XRBodyTracker::JOINT_RIGHT_SHOULDER"
"RightUpperArm", // XRBodyTracker::JOINT_RIGHT_UPPER_ARM"
"RightLowerArm", // XRBodyTracker::JOINT_RIGHT_LOWER_ARM"
// Lower Body Joints.
"LeftUpperLeg", // XRBodyTracker::JOINT_LEFT_UPPER_LEG
"LeftLowerLeg", // XRBodyTracker::JOINT_LEFT_LOWER_LEG
"LeftFoot", // XRBodyTracker::JOINT_LEFT_FOOT
"LeftToes", // XRBodyTracker::JOINT_LEFT_TOES
"RightUpperLeg", // XRBodyTracker::JOINT_RIGHT_UPPER_LEG
"RightLowerLeg", // XRBodyTracker::JOINT_RIGHT_LOWER_LEG
"RightFoot", // XRBodyTracker::JOINT_RIGHT_FOOT
"RightToes", // XRBodyTracker::JOINT_RIGHT_TOES
// Left Hand Joints.
"LeftHand", // XRBodyTracker::JOINT_LEFT_HAND
"LeftPalm", // XRBodyTracker::JOINT_LEFT_PALM
"LeftWrist", // XRBodyTracker::JOINT_LEFT_WRIST
"LeftThumbMetacarpal", // XRBodyTracker::JOINT_LEFT_THUMB_METACARPAL
"LeftThumbProximal", // XRBodyTracker::JOINT_LEFT_THUMB_PHALANX_PROXIMAL
"LeftThumbDistal", // XRBodyTracker::JOINT_LEFT_THUMB_PHALANX_DISTAL
"LeftThumbTip", // XRBodyTracker::JOINT_LEFT_THUMB_TIP
"LeftIndexMetacarpal", // XRBodyTracker::JOINT_LEFT_INDEX_FINGER_METACARPAL
"LeftIndexProximal", // XRBodyTracker::JOINT_LEFT_INDEX_FINGER_PHALANX_PROXIMAL
"LeftIndexIntermediate", // XRBodyTracker::JOINT_LEFT_INDEX_FINGER_PHALANX_INTERMEDIATE
"LeftIndexDistal", // XRBodyTracker::JOINT_LEFT_INDEX_FINGER_PHALANX_DISTAL
"LeftIndexTip", // XRBodyTracker::JOINT_LEFT_INDEX_FINGER_TIP
"LeftMiddleMetacarpal", // XRBodyTracker::JOINT_LEFT_MIDDLE_FINGER_METACARPAL
"LeftMiddleProximal", // XRBodyTracker::JOINT_LEFT_MIDDLE_FINGER_PHALANX_PROXIMAL
"LeftMiddleIntermediate", // XRBodyTracker::JOINT_LEFT_MIDDLE_FINGER_PHALANX_INTERMEDIATE
"LeftMiddleDistal", // XRBodyTracker::JOINT_LEFT_MIDDLE_FINGER_PHALANX_DISTAL
"LeftMiddleTip", // XRBodyTracker::JOINT_LEFT_MIDDLE_FINGER_TIP
"LeftRingMetacarpal", // XRBodyTracker::JOINT_LEFT_RING_FINGER_METACARPAL
"LeftRingProximal", // XRBodyTracker::JOINT_LEFT_RING_FINGER_PHALANX_PROXIMAL
"LeftRingIntermediate", // XRBodyTracker::JOINT_LEFT_RING_FINGER_PHALANX_INTERMEDIATE
"LeftRingDistal", // XRBodyTracker::JOINT_LEFT_RING_FINGER_PHALANX_DISTAL
"LeftRingTip", // XRBodyTracker::JOINT_LEFT_RING_FINGER_TIP
"LeftLittleMetacarpal", // XRBodyTracker::JOINT_LEFT_PINKY_FINGER_METACARPAL
"LeftLittleProximal", // XRBodyTracker::JOINT_LEFT_PINKY_FINGER_PHALANX_PROXIMAL
"LeftLittleIntermediate", // XRBodyTracker::JOINT_LEFT_PINKY_FINGER_PHALANX_INTERMEDIATE
"LeftLittleDistal", // XRBodyTracker::JOINT_LEFT_PINKY_FINGER_PHALANX_DISTAL
"LeftLittleTip", // XRBodyTracker::JOINT_LEFT_PINKY_FINGER_TIP
// Right Hand Joints.
"RightHand", // XRBodyTracker::JOINT_RIGHT_HAND
"RightPalm", // XRBodyTracker::JOINT_RIGHT_PALM
"RightWrist", // XRBodyTracker::JOINT_RIGHT_WRIST
"RightThumbMetacarpal", // XRBodyTracker::JOINT_RIGHT_THUMB_METACARPAL
"RightThumbProximal", // XRBodyTracker::JOINT_RIGHT_THUMB_PHALANX_PROXIMAL
"RightThumbDistal", // XRBodyTracker::JOINT_RIGHT_THUMB_PHALANX_DISTAL
"RightThumbTip", // XRBodyTracker::JOINT_RIGHT_THUMB_TIP
"RightIndexMetacarpal", // XRBodyTracker::JOINT_RIGHT_INDEX_FINGER_METACARPAL
"RightIndexProximal", // XRBodyTracker::JOINT_RIGHT_INDEX_FINGER_PHALANX_PROXIMAL
"RightIndexIntermediate", // XRBodyTracker::JOINT_RIGHT_INDEX_FINGER_PHALANX_INTERMEDIATE
"RightIndexDistal", // XRBodyTracker::JOINT_RIGHT_INDEX_FINGER_PHALANX_DISTAL
"RightIndexTip", // XRBodyTracker::JOINT_RIGHT_INDEX_FINGER_TIP
"RightMiddleMetacarpal", // XRBodyTracker::JOINT_RIGHT_MIDDLE_FINGER_METACARPAL
"RightMiddleProximal", // XRBodyTracker::JOINT_RIGHT_MIDDLE_FINGER_PHALANX_PROXIMAL
"RightMiddleIntermediate", // XRBodyTracker::JOINT_RIGHT_MIDDLE_FINGER_PHALANX_INTERMEDIATE
"RightMiddleDistal", // XRBodyTracker::JOINT_RIGHT_MIDDLE_FINGER_PHALANX_DISTAL
"RightMiddleTip", // XRBodyTracker::JOINT_RIGHT_MIDDLE_FINGER_TIP
"RightRingMetacarpal", // XRBodyTracker::JOINT_RIGHT_RING_FINGER_METACARPAL
"RightRingProximal", // XRBodyTracker::JOINT_RIGHT_RING_FINGER_PHALANX_PROXIMAL
"RightRingIntermediate", // XRBodyTracker::JOINT_RIGHT_RING_FINGER_PHALANX_INTERMEDIATE
"RightRingDistal", // XRBodyTracker::JOINT_RIGHT_RING_FINGER_PHALANX_DISTAL
"RightRingTip", // XRBodyTracker::JOINT_RIGHT_RING_FINGER_TIP
"RightLittleMetacarpal", // XRBodyTracker::JOINT_RIGHT_PINKY_FINGER_METACARPAL
"RightLittleProximal", // XRBodyTracker::JOINT_RIGHT_PINKY_FINGER_PHALANX_PROXIMAL
"RightLittleIntermediate", // XRBodyTracker::JOINT_RIGHT_PINKY_FINGER_PHALANX_INTERMEDIATE
"RightLittleDistal", // XRBodyTracker::JOINT_RIGHT_PINKY_FINGER_PHALANX_DISTAL
"RightLittleTip", // XRBodyTracker::JOINT_RIGHT_PINKY_FINGER_TIP
// Extra Joints.
"LowerChest", // XRBodyTracker::JOINT_LOWER_CHEST
"LeftScapula", // XRBodyTracker::JOINT_LEFT_SCAPULA
"LeftWristTwist", // XRBodyTracker::JOINT_LEFT_WRIST_TWIST
"RightScapula", // XRBodyTracker::JOINT_RIGHT_SCAPULA
"RightWristTwist", // XRBodyTracker::JOINT_RIGHT_WRIST_TWIST
"LeftFootTwist", // XRBodyTracker::JOINT_LEFT_FOOT_TWIST
"LeftHeel", // XRBodyTracker::JOINT_LEFT_HEEL
"LeftMiddleFoot", // XRBodyTracker::JOINT_LEFT_MIDDLE_FOOT
"RightFootTwist", // XRBodyTracker::JOINT_RIGHT_FOOT_TWIST
"RightHeel", // XRBodyTracker::JOINT_RIGHT_HEEL
"RightMiddleFoot", // XRBodyTracker::JOINT_RIGHT_MIDDLE_FOOT
};
// reset JIC.
for (int i = 0; i < XRBodyTracker::JOINT_MAX; i++) {
joints[i].bone = -1;
joints[i].parent_joint = -1;
}
const Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
// Find the skeleton-bones associated with each joint.
int bones[XRBodyTracker::JOINT_MAX];
for (int i = 0; i < XRBodyTracker::JOINT_MAX; i++) {
// Skip upper body joints if not enabled.
if (!body_update.has_flag(BODY_UPDATE_UPPER_BODY) && i >= XRBodyTracker::JOINT_HIPS && i <= XRBodyTracker::JOINT_RIGHT_LOWER_ARM) {
bones[i] = -1;
continue;
}
// Skip lower body joints if not enabled.
if (!body_update.has_flag(BODY_UPDATE_LOWER_BODY) && i >= XRBodyTracker::JOINT_LEFT_UPPER_LEG && i <= XRBodyTracker::JOINT_RIGHT_TOES) {
bones[i] = -1;
continue;
}
// Skip hand joints if not enabled.
if (!body_update.has_flag(BODY_UPDATE_HANDS) && i >= XRBodyTracker::JOINT_LEFT_HAND && i <= XRBodyTracker::JOINT_RIGHT_PINKY_FINGER_TIP) {
bones[i] = -1;
continue;
}
// Find the skeleton bone.
bones[i] = skeleton->find_bone(bone_names[i]);
if (bones[i] == -1) {
WARN_PRINT(vformat("Couldn't obtain bone for %s", bone_names[i]));
}
}
// Assemble the joint relationship to the available skeleton bones.
for (int i = 0; i < XRBodyTracker::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 the root joint.
joints[i].bone = bone;
joints[i].parent_joint = XRBodyTracker::JOINT_ROOT;
continue;
}
// Find the joint associated with the parent skeleton-bone.
for (int j = 0; j < XRBodyTracker::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 XRBodyModifier3D::_process_modification(double p_delta) {
Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
const Ref<XRBodyTracker> tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
return;
}
// Skip if no tracking data.
if (!tracker->get_has_tracking_data()) {
return;
}
// Get the world and skeleton scale.
const float ss = skeleton->get_motion_scale();
// Read the relevant tracking data. This applies the skeleton motion scale to
// the joint transforms, allowing the tracking data to be scaled to the skeleton.
bool has_valid_data[XRBodyTracker::JOINT_MAX];
Transform3D transforms[XRBodyTracker::JOINT_MAX];
Transform3D inv_transforms[XRBodyTracker::JOINT_MAX];
for (int joint = 0; joint < XRBodyTracker::JOINT_MAX; joint++) {
BitField<XRBodyTracker::JointFlags> flags = tracker->get_joint_flags(static_cast<XRBodyTracker::Joint>(joint));
has_valid_data[joint] = flags.has_flag(XRBodyTracker::JOINT_FLAG_ORIENTATION_VALID) && flags.has_flag(XRBodyTracker::JOINT_FLAG_POSITION_VALID);
if (has_valid_data[joint]) {
transforms[joint] = tracker->get_joint_transform(static_cast<XRBodyTracker::Joint>(joint));
transforms[joint].origin *= ss;
inv_transforms[joint] = transforms[joint].inverse();
}
}
// Skip if root joint not tracked.
if (!has_valid_data[XRBodyTracker::JOINT_ROOT]) {
return;
}
// Apply the joint information to the skeleton.
for (int joint = 0; joint < XRBodyTracker::JOINT_MAX; joint++) {
// Skip if no valid joint data
if (!has_valid_data[joint]) {
continue;
}
// 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, or if the joint is the hips, to allow
// for climbing or crouching.
if (bone_update == BONE_UPDATE_FULL || joint == XRBodyTracker::JOINT_HIPS) {
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));
}
}
void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, XRServer::TrackerType p_tracker_type) {
if (tracker_name == p_tracker_name) {
_get_joint_data();
}
}
void XRBodyModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) {
_get_joint_data();
}
void XRBodyModifier3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
xr_server->connect("tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
xr_server->connect("tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
xr_server->connect("tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
}
_get_joint_data();
} break;
case NOTIFICATION_EXIT_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
xr_server->disconnect("tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
xr_server->disconnect("tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
xr_server->disconnect("tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
}
for (int i = 0; i < XRBodyTracker::JOINT_MAX; i++) {
joints[i].bone = -1;
joints[i].parent_joint = -1;
}
} break;
default: {
} break;
}
}

View File

@@ -0,0 +1,92 @@
/**************************************************************************/
/* xr_body_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/skeleton_modifier_3d.h"
#include "servers/xr/xr_body_tracker.h"
class Skeleton3D;
/**
The XRBodyModifier3D node drives a body skeleton using body tracking
data from an XRBodyTracker instance.
*/
class XRBodyModifier3D : public SkeletonModifier3D {
GDCLASS(XRBodyModifier3D, SkeletonModifier3D);
public:
enum BodyUpdate {
BODY_UPDATE_UPPER_BODY = 1,
BODY_UPDATE_LOWER_BODY = 2,
BODY_UPDATE_HANDS = 4,
};
enum BoneUpdate {
BONE_UPDATE_FULL,
BONE_UPDATE_ROTATION_ONLY,
BONE_UPDATE_MAX
};
void set_body_tracker(const StringName &p_tracker_name);
StringName get_body_tracker() const;
void set_body_update(BitField<BodyUpdate> p_body_update);
BitField<BodyUpdate> get_body_update() const;
void set_bone_update(BoneUpdate p_bone_update);
BoneUpdate get_bone_update() const;
void _notification(int p_what);
protected:
static void _bind_methods();
virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override;
virtual void _process_modification(double p_delta) override;
private:
struct JointData {
int bone = -1;
int parent_joint = -1;
};
StringName tracker_name = "/user/body_tracker";
BitField<BodyUpdate> body_update = BODY_UPDATE_UPPER_BODY | BODY_UPDATE_LOWER_BODY | BODY_UPDATE_HANDS;
BoneUpdate bone_update = BONE_UPDATE_FULL;
JointData joints[XRBodyTracker::JOINT_MAX];
void _get_joint_data();
void _tracker_changed(const StringName &p_tracker_name, XRServer::TrackerType p_tracker_type);
};
VARIANT_BITFIELD_CAST(XRBodyModifier3D::BodyUpdate)
VARIANT_ENUM_CAST(XRBodyModifier3D::BoneUpdate)

View File

@@ -0,0 +1,615 @@
/**************************************************************************/
/* xr_face_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_face_modifier_3d.h"
#include "servers/xr/xr_face_tracker.h"
#include "servers/xr_server.h"
// This method takes the name of a mesh blend shape and returns the
// corresponding XRFaceTracker blend shape. If no match is
// found then the function returns -1.
static int find_face_blend_shape(const StringName &p_name) {
// Entry for blend shape name table.
struct blend_map_entry {
int blend;
const char *name[4];
};
// Table of blend shape names.
//
// This table consists of the XRFaceTracker blend shape and
// the corresponding names (lowercase and no underscore) of:
// - The Unified Expression blend shape name.
// - The ARKit blend shape name (if present and different).
// - The SRanipal blend shape name (if present and different).
// - The Meta blend shape name (if present and different).
static constexpr blend_map_entry blend_map[] = {
{ XRFaceTracker::FT_EYE_LOOK_OUT_RIGHT,
{ "eyelookoutright", "eyerightright", "eyeslookoutr" } },
{ XRFaceTracker::FT_EYE_LOOK_IN_RIGHT,
{ "eyelookinright", "eyerightleft", "eyeslookinr" } },
{ XRFaceTracker::FT_EYE_LOOK_UP_RIGHT,
{ "eyelookupright", "eyerightlookup", "eyeslookupr" } },
{ XRFaceTracker::FT_EYE_LOOK_DOWN_RIGHT,
{ "eyelookdownright", "eyerightlookdown", "eyeslookdownr" } },
{ XRFaceTracker::FT_EYE_LOOK_OUT_LEFT,
{ "eyelookoutleft", "eyeleftleft", "eyeslookoutl" } },
{ XRFaceTracker::FT_EYE_LOOK_IN_LEFT,
{ "eyelookinleft", "eyeleftright", "eyeslookinl" } },
{ XRFaceTracker::FT_EYE_LOOK_UP_LEFT,
{ "eyelookupleft", "eyeleftlookup", "eyeslookupl" } },
{ XRFaceTracker::FT_EYE_LOOK_DOWN_LEFT,
{ "eyelookdownleft", "eyeleftlookdown", "eyeslookdownl" } },
{ XRFaceTracker::FT_EYE_CLOSED_RIGHT,
{ "eyeclosedright", "eyeblinkright", "eyerightblink", "eyesclosedr" } },
{ XRFaceTracker::FT_EYE_CLOSED_LEFT,
{ "eyeclosedleft", "eyeblinkleft", "eyeleftblink", "eyesclosedl" } },
{ XRFaceTracker::FT_EYE_SQUINT_RIGHT,
{ "eyesquintright", "eyessquintr" } },
{ XRFaceTracker::FT_EYE_SQUINT_LEFT,
{ "eyesquintleft", "eyessquintl" } },
{ XRFaceTracker::FT_EYE_WIDE_RIGHT,
{ "eyewideright", "eyerightwide", "eyeswidenr" } },
{ XRFaceTracker::FT_EYE_WIDE_LEFT,
{ "eyewideleft", "eyeleftwide", "eyeswidenl" } },
{ XRFaceTracker::FT_EYE_DILATION_RIGHT,
{ "eyedilationright", "eyerightdilation" } },
{ XRFaceTracker::FT_EYE_DILATION_LEFT,
{ "eyedilationleft", "eyeleftdilation" } },
{ XRFaceTracker::FT_EYE_CONSTRICT_RIGHT,
{ "eyeconstrictright", "eyerightconstrict" } },
{ XRFaceTracker::FT_EYE_CONSTRICT_LEFT,
{ "eyeconstrictleft", "eyeleftconstrict" } },
{ XRFaceTracker::FT_BROW_PINCH_RIGHT,
{ "browpinchright" } },
{ XRFaceTracker::FT_BROW_PINCH_LEFT,
{ "browpinchleft" } },
{ XRFaceTracker::FT_BROW_LOWERER_RIGHT,
{ "browlowererright" } },
{ XRFaceTracker::FT_BROW_LOWERER_LEFT,
{ "browlowererleft" } },
{ XRFaceTracker::FT_BROW_INNER_UP_RIGHT,
{ "browinnerupright", "innerbrowraiserr" } },
{ XRFaceTracker::FT_BROW_INNER_UP_LEFT,
{ "browinnerupleft", "innerbrowraiserl" } },
{ XRFaceTracker::FT_BROW_OUTER_UP_RIGHT,
{ "browouterupright", "outerbrowraiserr" } },
{ XRFaceTracker::FT_BROW_OUTER_UP_LEFT,
{ "browouterupleft", "outerbrowraiserl" } },
{ XRFaceTracker::FT_NOSE_SNEER_RIGHT,
{ "nosesneerright", "nosewrinklerr" } },
{ XRFaceTracker::FT_NOSE_SNEER_LEFT,
{ "nosesneerleft", "nosewrinklerl" } },
{ XRFaceTracker::FT_NASAL_DILATION_RIGHT,
{ "nasaldilationright" } },
{ XRFaceTracker::FT_NASAL_DILATION_LEFT,
{ "nasaldilationleft" } },
{ XRFaceTracker::FT_NASAL_CONSTRICT_RIGHT,
{ "nasalconstrictright" } },
{ XRFaceTracker::FT_NASAL_CONSTRICT_LEFT,
{ "nasalconstrictleft" } },
{ XRFaceTracker::FT_CHEEK_SQUINT_RIGHT,
{ "cheeksquintright", "cheekraiserr" } },
{ XRFaceTracker::FT_CHEEK_SQUINT_LEFT,
{ "cheeksquintleft", "cheekraiserl" } },
{ XRFaceTracker::FT_CHEEK_PUFF_RIGHT,
{ "cheekpuffright", "cheekpuffr" } },
{ XRFaceTracker::FT_CHEEK_PUFF_LEFT,
{ "cheekpuffleft", "cheekpuffl" } },
{ XRFaceTracker::FT_CHEEK_SUCK_RIGHT,
{ "cheeksuckright", "cheeksuckr" } },
{ XRFaceTracker::FT_CHEEK_SUCK_LEFT,
{ "cheeksuckleft", "cheeksuckl" } },
{ XRFaceTracker::FT_JAW_OPEN,
{ "jawopen", "jawdrop" } },
{ XRFaceTracker::FT_MOUTH_CLOSED,
{ "mouthclosed", "mouthclose", "mouthapeshape", "lipstoward" } },
{ XRFaceTracker::FT_JAW_RIGHT,
{ "jawright", "jawsidewaysright" } },
{ XRFaceTracker::FT_JAW_LEFT,
{ "jawleft", "jawsidewaysleft" } },
{ XRFaceTracker::FT_JAW_FORWARD,
{ "jawforward", "jawthrust" } },
{ XRFaceTracker::FT_JAW_BACKWARD,
{ "jawbackward" } },
{ XRFaceTracker::FT_JAW_CLENCH,
{ "jawclench" } },
{ XRFaceTracker::FT_JAW_MANDIBLE_RAISE,
{ "jawmandibleraise" } },
{ XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT,
{ "lipsuckupperright", "lipsuckrt" } },
{ XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT,
{ "lipsuckupperleft", "lipsucklt" } },
{ XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT,
{ "lipsucklowerright", "lipsuckrb" } },
{ XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT,
{ "lipsucklowerleft", "lipsucklb" } },
{ XRFaceTracker::FT_LIP_SUCK_CORNER_RIGHT,
{ "lipsuckcornerright" } },
{ XRFaceTracker::FT_LIP_SUCK_CORNER_LEFT,
{ "lipsuckcornerleft" } },
{ XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT,
{ "lipfunnelupperright", "lipfunnelerrt" } },
{ XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT,
{ "lipfunnelupperleft", "lipfunnelerlt" } },
{ XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT,
{ "lipfunnellowerright", "lipsuckrb" } },
{ XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT,
{ "lipfunnellowerleft", "lipsucklb" } },
{ XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT,
{ "lippuckerupperright" } },
{ XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT,
{ "lippuckerupperleft" } },
{ XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT,
{ "lippuckerlowerright" } },
{ XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT,
{ "lippuckerlowerleft" } },
{ XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT,
{ "mouthupperupright", "upperlipraiserr" } },
{ XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT,
{ "mouthupperupleft", "upperlipraiserl" } },
{ XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT,
{ "mouthlowerdownright", "mouthlowerupright", "lowerlipdepressorr" } },
{ XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT,
{ "mouthlowerdownleft", "mouthlowerupleft", "lowerlipdepressorl" } },
{ XRFaceTracker::FT_MOUTH_UPPER_DEEPEN_RIGHT,
{ "mouthupperdeepenright" } },
{ XRFaceTracker::FT_MOUTH_UPPER_DEEPEN_LEFT,
{ "mouthupperdeepenleft" } },
{ XRFaceTracker::FT_MOUTH_UPPER_RIGHT,
{ "mouthupperright" } },
{ XRFaceTracker::FT_MOUTH_UPPER_LEFT,
{ "mouthupperleft" } },
{ XRFaceTracker::FT_MOUTH_LOWER_RIGHT,
{ "mouthlowerright" } },
{ XRFaceTracker::FT_MOUTH_LOWER_LEFT,
{ "mouthlowerleft" } },
{ XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT,
{ "mouthcornerpullright" } },
{ XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT,
{ "mouthcornerpullleft" } },
{ XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT,
{ "mouthcornerslantright" } },
{ XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT,
{ "mouthcornerslantleft" } },
{ XRFaceTracker::FT_MOUTH_FROWN_RIGHT,
{ "mouthfrownright", "lipcornerdepressorr" } },
{ XRFaceTracker::FT_MOUTH_FROWN_LEFT,
{ "mouthfrownleft", "lipcornerdepressorl" } },
{ XRFaceTracker::FT_MOUTH_STRETCH_RIGHT,
{ "mouthstretchright", "lipstretcherr" } },
{ XRFaceTracker::FT_MOUTH_STRETCH_LEFT,
{ "mouthstretchleft", "lipstretcherl" } },
{ XRFaceTracker::FT_MOUTH_DIMPLE_RIGHT,
{ "mouthdimplerright", "mouthdimpleright", "dimplerr" } },
{ XRFaceTracker::FT_MOUTH_DIMPLE_LEFT,
{ "mouthdimplerleft", "mouthdimpleleft", "dimplerl" } },
{ XRFaceTracker::FT_MOUTH_RAISER_UPPER,
{ "mouthraiserupper", "mouthshrugupper", "chinraisert" } },
{ XRFaceTracker::FT_MOUTH_RAISER_LOWER,
{ "mouthraiserlower", "mouthshruglower", "mouthloweroverlay", "chinraiserb" } },
{ XRFaceTracker::FT_MOUTH_PRESS_RIGHT,
{ "mouthpressright", "lippressorr" } },
{ XRFaceTracker::FT_MOUTH_PRESS_LEFT,
{ "mouthpressleft", "lippressorl" } },
{ XRFaceTracker::FT_MOUTH_TIGHTENER_RIGHT,
{ "mouthtightenerright", "liptightenerr" } },
{ XRFaceTracker::FT_MOUTH_TIGHTENER_LEFT,
{ "mouthtightenerleft", "liptightenerl" } },
{ XRFaceTracker::FT_TONGUE_OUT,
{ "tongueout", "tonguelongstep2" } },
{ XRFaceTracker::FT_TONGUE_UP,
{ "tongueup" } },
{ XRFaceTracker::FT_TONGUE_DOWN,
{ "tonguedown" } },
{ XRFaceTracker::FT_TONGUE_RIGHT,
{ "tongueright" } },
{ XRFaceTracker::FT_TONGUE_LEFT,
{ "tongueleft" } },
{ XRFaceTracker::FT_TONGUE_ROLL,
{ "tongueroll" } },
{ XRFaceTracker::FT_TONGUE_BLEND_DOWN,
{ "tongueblenddown" } },
{ XRFaceTracker::FT_TONGUE_CURL_UP,
{ "tonguecurlup" } },
{ XRFaceTracker::FT_TONGUE_SQUISH,
{ "tonguesquish" } },
{ XRFaceTracker::FT_TONGUE_FLAT,
{ "tongueflat" } },
{ XRFaceTracker::FT_TONGUE_TWIST_RIGHT,
{ "tonguetwistright" } },
{ XRFaceTracker::FT_TONGUE_TWIST_LEFT,
{ "tonguetwistleft" } },
{ XRFaceTracker::FT_SOFT_PALATE_CLOSE,
{ "softpalateclose" } },
{ XRFaceTracker::FT_THROAT_SWALLOW,
{ "throatswallow" } },
{ XRFaceTracker::FT_NECK_FLEX_RIGHT,
{ "neckflexright" } },
{ XRFaceTracker::FT_NECK_FLEX_LEFT,
{ "neckflexleft" } },
{ XRFaceTracker::FT_EYE_CLOSED,
{ "eyeclosed" } },
{ XRFaceTracker::FT_EYE_WIDE,
{ "eyewide" } },
{ XRFaceTracker::FT_EYE_SQUINT,
{ "eyesquint" } },
{ XRFaceTracker::FT_EYE_DILATION,
{ "eyedilation" } },
{ XRFaceTracker::FT_EYE_CONSTRICT,
{ "eyeconstrict" } },
{ XRFaceTracker::FT_BROW_DOWN_RIGHT,
{ "browdownright", "browlowererr" } },
{ XRFaceTracker::FT_BROW_DOWN_LEFT,
{ "browdownleft", "browlowererl" } },
{ XRFaceTracker::FT_BROW_DOWN,
{ "browdown" } },
{ XRFaceTracker::FT_BROW_UP_RIGHT,
{ "browupright" } },
{ XRFaceTracker::FT_BROW_UP_LEFT,
{ "browupleft" } },
{ XRFaceTracker::FT_BROW_UP,
{ "browup" } },
{ XRFaceTracker::FT_NOSE_SNEER,
{ "nosesneer" } },
{ XRFaceTracker::FT_NASAL_DILATION,
{ "nasaldilation" } },
{ XRFaceTracker::FT_NASAL_CONSTRICT,
{ "nasalconstrict" } },
{ XRFaceTracker::FT_CHEEK_PUFF,
{ "cheekpuff" } },
{ XRFaceTracker::FT_CHEEK_SUCK,
{ "cheeksuck" } },
{ XRFaceTracker::FT_CHEEK_SQUINT,
{ "cheeksquint" } },
{ XRFaceTracker::FT_LIP_SUCK_UPPER,
{ "lipsuckupper", "mouthrollupper", "mouthupperinside" } },
{ XRFaceTracker::FT_LIP_SUCK_LOWER,
{ "lipsucklower", "mouthrolllower", "mouthlowerinside" } },
{ XRFaceTracker::FT_LIP_SUCK,
{ "lipsuck" } },
{ XRFaceTracker::FT_LIP_FUNNEL_UPPER,
{ "lipfunnelupper", "mouthupperoverturn" } },
{ XRFaceTracker::FT_LIP_FUNNEL_LOWER,
{ "lipfunnellower", "mouthloweroverturn" } },
{ XRFaceTracker::FT_LIP_FUNNEL,
{ "lipfunnel", "mouthfunnel" } },
{ XRFaceTracker::FT_LIP_PUCKER_UPPER,
{ "lippuckerupper" } },
{ XRFaceTracker::FT_LIP_PUCKER_LOWER,
{ "lippuckerlower" } },
{ XRFaceTracker::FT_LIP_PUCKER,
{ "lippucker", "mouthpucker", "mouthpout" } },
{ XRFaceTracker::FT_MOUTH_UPPER_UP,
{ "mouthupperup" } },
{ XRFaceTracker::FT_MOUTH_LOWER_DOWN,
{ "mouthlowerdown" } },
{ XRFaceTracker::FT_MOUTH_OPEN,
{ "mouthopen" } },
{ XRFaceTracker::FT_MOUTH_RIGHT,
{ "mouthright" } },
{ XRFaceTracker::FT_MOUTH_LEFT,
{ "mouthleft" } },
{ XRFaceTracker::FT_MOUTH_SMILE_RIGHT,
{ "mouthsmileright", "lipcornerpullerr" } },
{ XRFaceTracker::FT_MOUTH_SMILE_LEFT,
{ "mouthsmileleft", "lipcornerpullerl" } },
{ XRFaceTracker::FT_MOUTH_SMILE,
{ "mouthsmile" } },
{ XRFaceTracker::FT_MOUTH_SAD_RIGHT,
{ "mouthsadright" } },
{ XRFaceTracker::FT_MOUTH_SAD_LEFT,
{ "mouthsadleft" } },
{ XRFaceTracker::FT_MOUTH_SAD,
{ "mouthsad" } },
{ XRFaceTracker::FT_MOUTH_STRETCH,
{ "mouthstretch" } },
{ XRFaceTracker::FT_MOUTH_DIMPLE,
{ "mouthdimple" } },
{ XRFaceTracker::FT_MOUTH_TIGHTENER,
{ "mouthtightener" } },
{ XRFaceTracker::FT_MOUTH_PRESS,
{ "mouthpress" } }
};
// Convert the name to lower-case and strip non-alphanumeric characters.
const String name = String(p_name).to_lower().remove_char('_');
// Iterate through the blend map.
for (const blend_map_entry &entry : blend_map) {
for (const char *n : entry.name) {
if (n == nullptr) {
break;
}
if (name == n) {
return entry.blend;
}
}
}
// Blend shape not found.
return -1;
}
// This method adds all the identified XRFaceTracker blend shapes of
// the mesh to the p_blend_mapping map. The map is indexed by the
// XRFaceTracker blend shape, and the value is the index of the mesh
// blend shape.
static void identify_face_blend_shapes(RBMap<int, int> &p_blend_mapping, const Ref<Mesh> &mesh) {
// Find all blend shapes.
const int count = mesh->get_blend_shape_count();
for (int i = 0; i < count; i++) {
const int blend = find_face_blend_shape(mesh->get_blend_shape_name(i));
if (blend >= 0) {
p_blend_mapping[blend] = i;
}
}
}
// This method removes any unified blend shapes from the p_blend_mapping map
// if all the individual blend shapes are found and going to be driven.
static void remove_driven_unified_blend_shapes(RBMap<int, int> &p_blend_mapping) {
// Entry for unified blend table.
struct unified_blend_entry {
int unified;
int individual[4];
};
// Table of unified blend shapes.
//
// This table consists of:
// - The XRFaceTracker unified blend shape.
// - The individual blend shapes that make up the unified blend shape.
static constexpr unified_blend_entry unified_blends[] = {
{ XRFaceTracker::FT_EYE_CLOSED,
{ XRFaceTracker::FT_EYE_CLOSED_RIGHT, XRFaceTracker::FT_EYE_CLOSED_LEFT, -1, -1 } },
{ XRFaceTracker::FT_EYE_WIDE,
{ XRFaceTracker::FT_EYE_WIDE_RIGHT, XRFaceTracker::FT_EYE_WIDE_LEFT, -1, -1 } },
{ XRFaceTracker::FT_EYE_SQUINT,
{ XRFaceTracker::FT_EYE_SQUINT_RIGHT, XRFaceTracker::FT_EYE_SQUINT_LEFT, -1, -1 } },
{ XRFaceTracker::FT_EYE_DILATION,
{ XRFaceTracker::FT_EYE_DILATION_RIGHT, XRFaceTracker::FT_EYE_DILATION_LEFT, -1, -1 } },
{ XRFaceTracker::FT_EYE_CONSTRICT,
{ XRFaceTracker::FT_EYE_CONSTRICT_RIGHT, XRFaceTracker::FT_EYE_CONSTRICT_LEFT, -1, -1 } },
{ XRFaceTracker::FT_BROW_DOWN_RIGHT,
{ XRFaceTracker::FT_BROW_LOWERER_RIGHT, XRFaceTracker::FT_BROW_PINCH_RIGHT, -1, -1 } },
{ XRFaceTracker::FT_BROW_DOWN_LEFT,
{ XRFaceTracker::FT_BROW_LOWERER_LEFT, XRFaceTracker::FT_BROW_PINCH_LEFT, -1, -1 } },
{ XRFaceTracker::FT_BROW_DOWN,
{ XRFaceTracker::FT_BROW_LOWERER_RIGHT, XRFaceTracker::FT_BROW_PINCH_RIGHT, XRFaceTracker::FT_BROW_LOWERER_LEFT, XRFaceTracker::FT_BROW_PINCH_LEFT } },
{ XRFaceTracker::FT_BROW_UP_RIGHT,
{ XRFaceTracker::FT_BROW_INNER_UP_RIGHT, XRFaceTracker::FT_BROW_OUTER_UP_RIGHT, -1, -1 } },
{ XRFaceTracker::FT_BROW_UP_LEFT,
{ XRFaceTracker::FT_BROW_INNER_UP_LEFT, XRFaceTracker::FT_BROW_OUTER_UP_LEFT, -1, -1 } },
{ XRFaceTracker::FT_BROW_UP,
{ XRFaceTracker::FT_BROW_INNER_UP_RIGHT, XRFaceTracker::FT_BROW_OUTER_UP_RIGHT, XRFaceTracker::FT_BROW_INNER_UP_LEFT, XRFaceTracker::FT_BROW_OUTER_UP_LEFT } },
{ XRFaceTracker::FT_NOSE_SNEER,
{ XRFaceTracker::FT_NOSE_SNEER_RIGHT, XRFaceTracker::FT_NOSE_SNEER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_NASAL_DILATION,
{ XRFaceTracker::FT_NASAL_DILATION_RIGHT, XRFaceTracker::FT_NASAL_DILATION_LEFT, -1, -1 } },
{ XRFaceTracker::FT_NASAL_CONSTRICT,
{ XRFaceTracker::FT_NASAL_CONSTRICT_RIGHT, XRFaceTracker::FT_NASAL_CONSTRICT_LEFT, -1, -1 } },
{ XRFaceTracker::FT_CHEEK_PUFF,
{ XRFaceTracker::FT_CHEEK_PUFF_RIGHT, XRFaceTracker::FT_CHEEK_PUFF_LEFT, -1, -1 } },
{ XRFaceTracker::FT_CHEEK_SUCK,
{ XRFaceTracker::FT_CHEEK_SUCK_RIGHT, XRFaceTracker::FT_CHEEK_SUCK_LEFT, -1, -1 } },
{ XRFaceTracker::FT_CHEEK_SQUINT,
{ XRFaceTracker::FT_CHEEK_SQUINT_RIGHT, XRFaceTracker::FT_CHEEK_SQUINT_LEFT, -1, -1 } },
{ XRFaceTracker::FT_LIP_SUCK_UPPER,
{ XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT, XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_LIP_SUCK_LOWER,
{ XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT, XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_LIP_SUCK,
{ XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT, XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT, XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT, XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT } },
{ XRFaceTracker::FT_LIP_FUNNEL_UPPER,
{ XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_LIP_FUNNEL_LOWER,
{ XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_LIP_FUNNEL,
{ XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT } },
{ XRFaceTracker::FT_LIP_PUCKER_UPPER,
{ XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_LIP_PUCKER_LOWER,
{ XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_LIP_PUCKER,
{ XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT, XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT } },
{ XRFaceTracker::FT_MOUTH_UPPER_UP,
{ XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT, XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_LOWER_DOWN,
{ XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_OPEN,
{ XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT, XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT } },
{ XRFaceTracker::FT_MOUTH_RIGHT,
{ XRFaceTracker::FT_MOUTH_UPPER_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_RIGHT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_LEFT,
{ XRFaceTracker::FT_MOUTH_UPPER_LEFT, XRFaceTracker::FT_MOUTH_LOWER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_SMILE_RIGHT,
{ XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_SMILE_LEFT,
{ XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_SMILE,
{ XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT } },
{ XRFaceTracker::FT_MOUTH_SAD_RIGHT,
{ XRFaceTracker::FT_MOUTH_FROWN_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_SAD_LEFT,
{ XRFaceTracker::FT_MOUTH_FROWN_LEFT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_SAD,
{ XRFaceTracker::FT_MOUTH_FROWN_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, XRFaceTracker::FT_MOUTH_FROWN_LEFT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT } },
{ XRFaceTracker::FT_MOUTH_STRETCH,
{ XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_DIMPLE,
{ XRFaceTracker::FT_MOUTH_DIMPLE_RIGHT, XRFaceTracker::FT_MOUTH_DIMPLE_LEFT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_TIGHTENER,
{ XRFaceTracker::FT_MOUTH_TIGHTENER_RIGHT, XRFaceTracker::FT_MOUTH_TIGHTENER_LEFT, -1, -1 } },
{ XRFaceTracker::FT_MOUTH_PRESS,
{ XRFaceTracker::FT_MOUTH_PRESS_RIGHT, XRFaceTracker::FT_MOUTH_PRESS_LEFT, -1, -1 } }
};
// Remove unified blend shapes if individual blend shapes are found.
for (const unified_blend_entry &entry : unified_blends) {
// Check if all individual blend shapes are found.
bool found = true;
for (const int i : entry.individual) {
if (i >= 0 && !p_blend_mapping.find(i)) {
found = false;
break;
}
}
// If all individual blend shapes are found then remove the unified blend shape.
if (found) {
p_blend_mapping.erase(entry.unified);
}
}
}
void XRFaceModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_face_tracker", "tracker_name"), &XRFaceModifier3D::set_face_tracker);
ClassDB::bind_method(D_METHOD("get_face_tracker"), &XRFaceModifier3D::get_face_tracker);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/face_tracker"), "set_face_tracker", "get_face_tracker");
ClassDB::bind_method(D_METHOD("set_target", "target"), &XRFaceModifier3D::set_target);
ClassDB::bind_method(D_METHOD("get_target"), &XRFaceModifier3D::get_target);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "MeshInstance3D"), "set_target", "get_target");
}
void XRFaceModifier3D::set_face_tracker(const StringName &p_tracker_name) {
tracker_name = p_tracker_name;
}
StringName XRFaceModifier3D::get_face_tracker() const {
return tracker_name;
}
void XRFaceModifier3D::set_target(const NodePath &p_target) {
target = p_target;
if (is_inside_tree()) {
_get_blend_data();
}
}
NodePath XRFaceModifier3D::get_target() const {
return target;
}
MeshInstance3D *XRFaceModifier3D::get_mesh_instance() const {
if (!has_node(target)) {
return nullptr;
}
Node *node = get_node(target);
if (!node) {
return nullptr;
}
return Object::cast_to<MeshInstance3D>(node);
}
void XRFaceModifier3D::_get_blend_data() {
// This method constructs the blend mapping from the XRFaceTracker
// blend shapes to the available blend shapes of the target mesh. It does this
// by:
//
// 1. Identifying the blend shapes of the target mesh and identifying what
// XRFaceTracker blend shape they correspond to. The results are
// placed in the blend_mapping map.
// 2. Prevent over-driving facial blend-shapes by removing any unified blend
// shapes from the map if all the individual blend shapes are already
// found and going to be driven.
blend_mapping.clear();
// Get the target MeshInstance3D.
const MeshInstance3D *mesh_instance = get_mesh_instance();
if (!mesh_instance) {
return;
}
// Get the mesh.
const Ref<Mesh> mesh = mesh_instance->get_mesh();
if (mesh.is_null()) {
return;
}
// Identify all face blend shapes and populate the map.
identify_face_blend_shapes(blend_mapping, mesh);
// Remove the unified blend shapes if all the individual blend shapes are found.
remove_driven_unified_blend_shapes(blend_mapping);
}
void XRFaceModifier3D::_update_face_blends() const {
// Get the XR Server.
const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
// Get the face tracker.
const Ref<XRFaceTracker> tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
return;
}
// Get the face mesh.
MeshInstance3D *mesh_instance = get_mesh_instance();
if (!mesh_instance) {
return;
}
// Get the blend weights.
const PackedFloat32Array weights = tracker->get_blend_shapes();
// Apply all the face blend weights to the mesh.
for (const KeyValue<int, int> &it : blend_mapping) {
mesh_instance->set_blend_shape_value(it.value, weights[it.key]);
}
}
void XRFaceModifier3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_get_blend_data();
set_process_internal(true);
} break;
case NOTIFICATION_EXIT_TREE: {
set_process_internal(false);
blend_mapping.clear();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
_update_face_blends();
} break;
default: {
} break;
}
}

View File

@@ -0,0 +1,70 @@
/**************************************************************************/
/* xr_face_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
/**
The XRFaceModifier3D node drives the blend shapes of a MeshInstance3D
with facial expressions from an XRFaceTracking instance.
The blend shapes provided by the mesh are interrogated, and used to
deduce an optimal mapping from the Unified Expressions blend shapes
provided by the XRFaceTracking instance to drive the face.
*/
class XRFaceModifier3D : public Node3D {
GDCLASS(XRFaceModifier3D, Node3D);
private:
StringName tracker_name = "/user/face_tracker";
NodePath target;
// Map from XRFaceTracker blend shape index to mesh blend shape index.
RBMap<int, int> blend_mapping;
MeshInstance3D *get_mesh_instance() const;
void _get_blend_data();
void _update_face_blends() const;
protected:
static void _bind_methods();
public:
void set_face_tracker(const StringName &p_tracker_name);
StringName get_face_tracker() const;
void set_target(const NodePath &p_target);
NodePath get_target() const;
void _notification(int p_what);
};

View File

@@ -0,0 +1,325 @@
/**************************************************************************/
/* 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 "core/config/project_settings.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_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/hand_tracker/left,/user/hand_tracker/right"), "set_hand_tracker", "get_hand_tracker");
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_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;
}
void XRHandModifier3D::_get_joint_data() {
if (!is_inside_tree()) {
return;
}
if (has_stored_previous_transforms) {
previous_relative_transforms.clear();
has_stored_previous_transforms = false;
}
// 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<bone>",
"Right<bone>",
};
// reset JIC
for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) {
joints[i].bone = -1;
joints[i].parent_joint = -1;
}
const Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
return;
}
// Verify we have a left or right hand tracker.
const XRPositionalTracker::TrackerHand tracker_hand = tracker->get_tracker_hand();
if (tracker_hand != XRPositionalTracker::TRACKER_HAND_LEFT &&
tracker_hand != XRPositionalTracker::TRACKER_HAND_RIGHT) {
return;
}
// Get the hand index (0 = left, 1 = right).
const int hand = tracker_hand == XRPositionalTracker::TRACKER_HAND_LEFT ? 0 : 1;
// 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>", 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::_process_modification(double p_delta) {
Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
return;
}
// Skip if no tracking data
if (!tracker->get_has_tracking_data()) {
if (!has_stored_previous_transforms) {
return;
}
// Apply previous relative transforms if they are stored.
for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
const int bone = joints[joint].bone;
if (bone == -1) {
continue;
}
if (bone_update == BONE_UPDATE_FULL) {
skeleton->set_bone_pose_position(joints[joint].bone, previous_relative_transforms[joint].origin);
}
skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(previous_relative_transforms[joint].basis));
}
return;
}
// Get the world and skeleton scale.
const float ss = skeleton->get_motion_scale();
// 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];
for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
BitField<XRHandTracker::HandJointFlags> 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 *= ss;
inv_transforms[joint] = transforms[joint].inverse();
}
}
// Skip if palm has no tracking data
if (!has_valid_data[XRHandTracker::HAND_JOINT_PALM]) {
return;
}
if (!has_stored_previous_transforms) {
previous_relative_transforms.resize(XRHandTracker::HAND_JOINT_MAX);
has_stored_previous_transforms = true;
}
Transform3D *previous_relative_transforms_ptr = previous_relative_transforms.ptrw();
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];
previous_relative_transforms_ptr[joint] = relative_transform;
// 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));
}
}
void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type) {
if (tracker_name == p_tracker_name) {
_get_joint_data();
}
}
void XRHandModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) {
_get_joint_data();
}
PackedStringArray XRHandModifier3D::get_configuration_warnings() const {
PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings();
// Detect OpenXR without the Hand Tracking extension.
if (GLOBAL_GET("xr/openxr/enabled") && !GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {
warnings.push_back("XRHandModifier3D requires the OpenXR Hand Tracking extension to be enabled.");
}
return warnings;
}
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("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
xr_server->connect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
xr_server->connect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed));
}
_get_joint_data();
} break;
case NOTIFICATION_EXIT_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
xr_server->disconnect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
xr_server->disconnect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
xr_server->disconnect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed));
}
for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) {
joints[i].bone = -1;
joints[i].parent_joint = -1;
}
} break;
default: {
} break;
}
}

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "scene/3d/skeleton_modifier_3d.h"
#include "servers/xr/xr_hand_tracker.h"
/**
The XRHandModifier3D node drives a hand skeleton using hand tracking
data from an XRHandTracking instance.
*/
class XRHandModifier3D : public SkeletonModifier3D {
GDCLASS(XRHandModifier3D, SkeletonModifier3D);
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_bone_update(BoneUpdate p_bone_update);
BoneUpdate get_bone_update() const;
PackedStringArray get_configuration_warnings() const override;
void _notification(int p_what);
protected:
static void _bind_methods();
virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override;
virtual void _process_modification(double p_delta) override;
private:
struct JointData {
int bone = -1;
int parent_joint = -1;
};
StringName tracker_name = "/user/hand_tracker/left";
BoneUpdate bone_update = BONE_UPDATE_FULL;
JointData joints[XRHandTracker::HAND_JOINT_MAX];
bool has_stored_previous_transforms = false;
Vector<Transform3D> previous_relative_transforms;
void _get_joint_data();
void _tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type);
};
VARIANT_ENUM_CAST(XRHandModifier3D::BoneUpdate)

846
scene/3d/xr/xr_nodes.cpp Normal file
View File

@@ -0,0 +1,846 @@
/**************************************************************************/
/* xr_nodes.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_nodes.h"
#include "core/config/project_settings.h"
#include "scene/main/viewport.h"
#include "servers/xr/xr_interface.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
void XRCamera3D::_bind_tracker() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_valid()) {
tracker->connect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed));
Ref<XRPose> pose = tracker->get_pose(pose_name);
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
}
}
}
void XRCamera3D::_unbind_tracker() {
if (tracker.is_valid()) {
tracker->disconnect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed));
}
tracker.unref();
}
void XRCamera3D::_changed_tracker(const StringName &p_tracker_name, int p_tracker_type) {
if (p_tracker_name == tracker_name) {
_bind_tracker();
}
}
void XRCamera3D::_removed_tracker(const StringName &p_tracker_name, int p_tracker_type) {
if (p_tracker_name == tracker_name) {
_unbind_tracker();
}
}
void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) {
if (p_pose->get_name() == pose_name) {
set_transform(p_pose->get_adjusted_transform());
}
}
void XRCamera3D::_physics_interpolated_changed() {
Camera3D::_physics_interpolated_changed();
update_configuration_warnings();
}
PackedStringArray XRCamera3D::get_configuration_warnings() const {
PackedStringArray warnings = Camera3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// Warn if the node has a parent which isn't an XROrigin3D!
Node *parent = get_parent();
XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent);
if (parent && origin == nullptr) {
warnings.push_back(RTR("XRCamera3D may not function as expected without an XROrigin3D node as its parent."));
};
if (SceneTree::is_fti_enabled_in_project() && is_physics_interpolated()) {
warnings.push_back(RTR("XRCamera3D should have physics_interpolation_mode set to OFF in order to avoid jitter."));
}
}
return warnings;
}
Vector3 XRCamera3D::project_local_ray_normal(const Point2 &p_pos) const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, Vector3());
Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
if (xr_interface.is_null()) {
// we might be in the editor or have VR turned off, just call superclass
return Camera3D::project_local_ray_normal(p_pos);
}
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene.");
Size2 viewport_size = get_viewport()->get_camera_rect_size();
Vector2 cpos = get_viewport()->get_camera_coords(p_pos);
Vector3 ray;
// Just use the first view, if multiple views are supported this function has no good result
Projection cm = xr_interface->get_projection_for_view(0, viewport_size.aspect(), get_near(), get_far());
Vector2 screen_he = cm.get_viewport_half_extents();
ray = Vector3(((cpos.x / viewport_size.width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (cpos.y / viewport_size.height)) * 2.0 - 1.0) * screen_he.y, -get_near()).normalized();
return ray;
}
Point2 XRCamera3D::unproject_position(const Vector3 &p_pos) const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, Vector2());
Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
if (xr_interface.is_null()) {
// we might be in the editor or have VR turned off, just call superclass
return Camera3D::unproject_position(p_pos);
}
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector2(), "Camera is not inside scene.");
Size2 viewport_size = get_viewport()->get_visible_rect().size;
// Just use the first view, if multiple views are supported this function has no good result
Projection cm = xr_interface->get_projection_for_view(0, viewport_size.aspect(), get_near(), get_far());
Plane p(get_camera_transform().xform_inv(p_pos), 1.0);
p = cm.xform4(p);
p.normal /= p.d;
Point2 res;
res.x = (p.normal.x * 0.5 + 0.5) * viewport_size.x;
res.y = (-p.normal.y * 0.5 + 0.5) * viewport_size.y;
return res;
}
Vector3 XRCamera3D::project_position(const Point2 &p_point, real_t p_z_depth) const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, Vector3());
Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
if (xr_interface.is_null()) {
// we might be in the editor or have VR turned off, just call superclass
return Camera3D::project_position(p_point, p_z_depth);
}
ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene.");
Size2 viewport_size = get_viewport()->get_visible_rect().size;
// Just use the first view, if multiple views are supported this function has no good result
Projection cm = xr_interface->get_projection_for_view(0, viewport_size.aspect(), get_near(), get_far());
Vector2 vp_he = cm.get_viewport_half_extents();
Vector2 point;
point.x = (p_point.x / viewport_size.x) * 2.0 - 1.0;
point.y = (1.0 - (p_point.y / viewport_size.y)) * 2.0 - 1.0;
point *= vp_he;
Vector3 p(point.x, point.y, -p_z_depth);
return get_camera_transform().xform(p);
}
Vector<Plane> XRCamera3D::get_frustum() const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, Vector<Plane>());
Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
if (xr_interface.is_null()) {
// we might be in the editor or have VR turned off, just call superclass
return Camera3D::get_frustum();
}
ERR_FAIL_COND_V(!is_inside_world(), Vector<Plane>());
Size2 viewport_size = get_viewport()->get_visible_rect().size;
// TODO Just use the first view for now, this is mostly for debugging so we may look into using our combined projection here.
Projection cm = xr_interface->get_projection_for_view(0, viewport_size.aspect(), get_near(), get_far());
return cm.get_projection_planes(get_camera_transform());
}
XRCamera3D::XRCamera3D() {
// XRCamera3D gets its transform updated every render frame and shouldn't be interpolated.
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
xr_server->connect("tracker_added", callable_mp(this, &XRCamera3D::_changed_tracker));
xr_server->connect("tracker_updated", callable_mp(this, &XRCamera3D::_changed_tracker));
xr_server->connect("tracker_removed", callable_mp(this, &XRCamera3D::_removed_tracker));
// check if our tracker already exists and if so, bind it...
_bind_tracker();
}
XRCamera3D::~XRCamera3D() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
xr_server->disconnect("tracker_added", callable_mp(this, &XRCamera3D::_changed_tracker));
xr_server->disconnect("tracker_updated", callable_mp(this, &XRCamera3D::_changed_tracker));
xr_server->disconnect("tracker_removed", callable_mp(this, &XRCamera3D::_removed_tracker));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// XRNode3D is a node that has it's transform updated by an XRPositionalTracker.
// Note that trackers are only available in runtime and only after an XRInterface registers one.
// So we bind by name and as long as a tracker isn't available, our node remains inactive.
void XRNode3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tracker", "tracker_name"), &XRNode3D::set_tracker);
ClassDB::bind_method(D_METHOD("get_tracker"), &XRNode3D::get_tracker);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "tracker", PROPERTY_HINT_ENUM_SUGGESTION), "set_tracker", "get_tracker");
ClassDB::bind_method(D_METHOD("set_pose_name", "pose"), &XRNode3D::set_pose_name);
ClassDB::bind_method(D_METHOD("get_pose_name"), &XRNode3D::get_pose_name);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "pose", PROPERTY_HINT_ENUM_SUGGESTION), "set_pose_name", "get_pose_name");
ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRNode3D::set_show_when_tracked);
ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRNode3D::get_show_when_tracked);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked");
ClassDB::bind_method(D_METHOD("get_is_active"), &XRNode3D::get_is_active);
ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRNode3D::get_has_tracking_data);
ClassDB::bind_method(D_METHOD("get_pose"), &XRNode3D::get_pose);
ClassDB::bind_method(D_METHOD("trigger_haptic_pulse", "action_name", "frequency", "amplitude", "duration_sec", "delay_sec"), &XRNode3D::trigger_haptic_pulse);
ADD_SIGNAL(MethodInfo("tracking_changed", PropertyInfo(Variant::BOOL, "tracking")));
}
void XRNode3D::_validate_property(PropertyInfo &p_property) const {
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
if (p_property.name == "tracker") {
PackedStringArray names = xr_server->get_suggested_tracker_names();
String hint_string;
for (const String &name : names) {
hint_string += name + ",";
}
p_property.hint_string = hint_string;
} else if (p_property.name == "pose") {
PackedStringArray names = xr_server->get_suggested_pose_names(tracker_name);
String hint_string;
for (const String &name : names) {
hint_string += name + ",";
}
p_property.hint_string = hint_string;
}
}
void XRNode3D::set_tracker(const StringName &p_tracker_name) {
if (tracker.is_valid() && tracker->get_tracker_name() == p_tracker_name) {
// didn't change
return;
}
// just in case
_unbind_tracker();
// copy the name
tracker_name = p_tracker_name;
pose_name = SceneStringName(default_);
// see if it's already available
_bind_tracker();
update_configuration_warnings();
notify_property_list_changed();
}
StringName XRNode3D::get_tracker() const {
return tracker_name;
}
void XRNode3D::set_pose_name(const StringName &p_pose_name) {
pose_name = p_pose_name;
// Update pose if we are bound to a tracker with a valid pose
Ref<XRPose> pose = get_pose();
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
}
}
StringName XRNode3D::get_pose_name() const {
return pose_name;
}
void XRNode3D::set_show_when_tracked(bool p_show) {
show_when_tracked = p_show;
_update_visibility();
}
bool XRNode3D::get_show_when_tracked() const {
return show_when_tracked;
}
bool XRNode3D::get_is_active() const {
if (tracker.is_null()) {
return false;
} else if (!tracker->has_pose(pose_name)) {
return false;
} else {
return true;
}
}
bool XRNode3D::get_has_tracking_data() const {
return has_tracking_data;
}
void XRNode3D::trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) {
// TODO need to link trackers to the interface that registered them so we can call this on the correct interface.
// For now this works fine as in 99% of the cases we only have our primary interface active
XRServer *xr_server = XRServer::get_singleton();
if (xr_server != nullptr) {
Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
if (xr_interface.is_valid()) {
xr_interface->trigger_haptic_pulse(p_action_name, tracker_name, p_frequency, p_amplitude, p_duration_sec, p_delay_sec);
}
}
}
Ref<XRPose> XRNode3D::get_pose() {
if (tracker.is_valid()) {
return tracker->get_pose(pose_name);
} else {
return Ref<XRPose>();
}
}
void XRNode3D::_bind_tracker() {
ERR_FAIL_COND_MSG(tracker.is_valid(), "Unbind the current tracker first");
XRServer *xr_server = XRServer::get_singleton();
if (xr_server != nullptr) {
tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
// It is possible and valid if the tracker isn't available (yet), in this case we just exit
return;
}
tracker->connect("pose_changed", callable_mp(this, &XRNode3D::_pose_changed));
tracker->connect("pose_lost_tracking", callable_mp(this, &XRNode3D::_pose_lost_tracking));
Ref<XRPose> pose = get_pose();
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);
}
}
}
void XRNode3D::_unbind_tracker() {
if (tracker.is_valid()) {
tracker->disconnect("pose_changed", callable_mp(this, &XRNode3D::_pose_changed));
tracker->disconnect("pose_lost_tracking", callable_mp(this, &XRNode3D::_pose_lost_tracking));
tracker.unref();
_set_has_tracking_data(false);
}
}
void XRNode3D::_changed_tracker(const StringName &p_tracker_name, int p_tracker_type) {
if (tracker_name == p_tracker_name) {
// just in case unref our current tracker
_unbind_tracker();
// get our new tracker
_bind_tracker();
}
}
void XRNode3D::_removed_tracker(const StringName &p_tracker_name, int p_tracker_type) {
if (tracker_name == p_tracker_name) {
// unref our tracker, it's no longer available
_unbind_tracker();
}
}
void XRNode3D::_pose_changed(const Ref<XRPose> &p_pose) {
if (p_pose.is_valid() && p_pose->get_name() == pose_name) {
set_transform(p_pose->get_adjusted_transform());
_set_has_tracking_data(p_pose->get_has_tracking_data());
}
}
void XRNode3D::_pose_lost_tracking(const Ref<XRPose> &p_pose) {
if (p_pose.is_valid() && p_pose->get_name() == pose_name) {
_set_has_tracking_data(false);
}
}
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;
}
// 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) {
// Only react to this if we have a primary interface.
XRServer *xr_server = XRServer::get_singleton();
if (xr_server != nullptr) {
Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
if (xr_interface.is_valid()) {
set_visible(has_tracking_data);
}
}
}
}
void XRNode3D::_physics_interpolated_changed() {
update_configuration_warnings();
}
XRNode3D::XRNode3D() {
// XRNode3D gets its transform updated every render frame and shouldn't be interpolated.
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
xr_server->connect("tracker_added", callable_mp(this, &XRNode3D::_changed_tracker));
xr_server->connect("tracker_updated", callable_mp(this, &XRNode3D::_changed_tracker));
xr_server->connect("tracker_removed", callable_mp(this, &XRNode3D::_removed_tracker));
}
XRNode3D::~XRNode3D() {
_unbind_tracker();
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
xr_server->disconnect("tracker_added", callable_mp(this, &XRNode3D::_changed_tracker));
xr_server->disconnect("tracker_updated", callable_mp(this, &XRNode3D::_changed_tracker));
xr_server->disconnect("tracker_removed", callable_mp(this, &XRNode3D::_removed_tracker));
}
PackedStringArray XRNode3D::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// Warn if the node has a parent which isn't an XROrigin3D!
Node *parent = get_parent();
XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent);
if (parent && origin == nullptr) {
warnings.push_back(RTR("XRNode3D may not function as expected without an XROrigin3D node as its parent."));
};
if (tracker_name == "") {
warnings.push_back(RTR("No tracker name is set."));
}
if (pose_name == "") {
warnings.push_back(RTR("No pose is set."));
}
if (SceneTree::is_fti_enabled_in_project() && is_physics_interpolated()) {
warnings.push_back(RTR("XRNode3D should have physics_interpolation_mode set to OFF in order to avoid jitter."));
}
}
return warnings;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void XRController3D::_bind_methods() {
// passthroughs to information about our related joystick
ClassDB::bind_method(D_METHOD("is_button_pressed", "name"), &XRController3D::is_button_pressed);
ClassDB::bind_method(D_METHOD("get_input", "name"), &XRController3D::get_input);
ClassDB::bind_method(D_METHOD("get_float", "name"), &XRController3D::get_float);
ClassDB::bind_method(D_METHOD("get_vector2", "name"), &XRController3D::get_vector2);
ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRController3D::get_tracker_hand);
ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::STRING, "name")));
ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::STRING, "name")));
ADD_SIGNAL(MethodInfo("input_float_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "value")));
ADD_SIGNAL(MethodInfo("input_vector2_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::VECTOR2, "value")));
ADD_SIGNAL(MethodInfo("profile_changed", PropertyInfo(Variant::STRING, "role")));
}
void XRController3D::_bind_tracker() {
XRNode3D::_bind_tracker();
if (tracker.is_valid()) {
// bind to input signals
tracker->connect("button_pressed", callable_mp(this, &XRController3D::_button_pressed));
tracker->connect("button_released", callable_mp(this, &XRController3D::_button_released));
tracker->connect("input_float_changed", callable_mp(this, &XRController3D::_input_float_changed));
tracker->connect("input_vector2_changed", callable_mp(this, &XRController3D::_input_vector2_changed));
tracker->connect("profile_changed", callable_mp(this, &XRController3D::_profile_changed));
}
}
void XRController3D::_unbind_tracker() {
if (tracker.is_valid()) {
// unbind input signals
tracker->disconnect("button_pressed", callable_mp(this, &XRController3D::_button_pressed));
tracker->disconnect("button_released", callable_mp(this, &XRController3D::_button_released));
tracker->disconnect("input_float_changed", callable_mp(this, &XRController3D::_input_float_changed));
tracker->disconnect("input_vector2_changed", callable_mp(this, &XRController3D::_input_vector2_changed));
tracker->disconnect("profile_changed", callable_mp(this, &XRController3D::_profile_changed));
}
XRNode3D::_unbind_tracker();
}
void XRController3D::_button_pressed(const String &p_name) {
emit_signal(SNAME("button_pressed"), p_name);
}
void XRController3D::_button_released(const String &p_name) {
emit_signal(SNAME("button_released"), p_name);
}
void XRController3D::_input_float_changed(const String &p_name, float p_value) {
emit_signal(SNAME("input_float_changed"), p_name, p_value);
}
void XRController3D::_input_vector2_changed(const String &p_name, Vector2 p_value) {
emit_signal(SNAME("input_vector2_changed"), p_name, p_value);
}
void XRController3D::_profile_changed(const String &p_role) {
emit_signal(SNAME("profile_changed"), p_role);
}
bool XRController3D::is_button_pressed(const StringName &p_name) const {
if (tracker.is_valid()) {
// Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type
bool pressed = tracker->get_input(p_name);
return pressed;
} else {
return false;
}
}
Variant XRController3D::get_input(const StringName &p_name) const {
if (tracker.is_valid()) {
return tracker->get_input(p_name);
} else {
return Variant();
}
}
float XRController3D::get_float(const StringName &p_name) const {
if (tracker.is_valid()) {
// Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type, but just in case we convert
Variant input = tracker->get_input(p_name);
switch (input.get_type()) {
case Variant::BOOL: {
bool value = input;
return value ? 1.0 : 0.0;
} break;
case Variant::FLOAT: {
float value = input;
return value;
} break;
default:
return 0.0;
};
} else {
return 0.0;
}
}
Vector2 XRController3D::get_vector2(const StringName &p_name) const {
if (tracker.is_valid()) {
// Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type, but just in case we convert
Variant input = tracker->get_input(p_name);
switch (input.get_type()) {
case Variant::BOOL: {
bool value = input;
return Vector2(value ? 1.0 : 0.0, 0.0);
} break;
case Variant::FLOAT: {
float value = input;
return Vector2(value, 0.0);
} break;
case Variant::VECTOR2: {
Vector2 axis = input;
return axis;
}
default:
return Vector2();
}
} else {
return Vector2();
}
}
XRPositionalTracker::TrackerHand XRController3D::get_tracker_hand() const {
// get our XRServer
if (tracker.is_null()) {
return XRPositionalTracker::TRACKER_HAND_UNKNOWN;
}
return tracker->get_tracker_hand();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void XRAnchor3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_size"), &XRAnchor3D::get_size);
ClassDB::bind_method(D_METHOD("get_plane"), &XRAnchor3D::get_plane);
}
Vector3 XRAnchor3D::get_size() const {
return size;
}
Plane XRAnchor3D::get_plane() const {
Vector3 location = get_position();
Basis orientation = get_transform().basis;
Plane plane(orientation.get_column(1).normalized(), location);
return plane;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
Vector<XROrigin3D *> XROrigin3D::origin_nodes;
PackedStringArray XROrigin3D::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
bool has_camera = false;
for (int i = 0; !has_camera && i < get_child_count(); i++) {
XRCamera3D *camera = Object::cast_to<XRCamera3D>(get_child(i));
if (camera) {
// found it!
has_camera = true;
}
}
if (!has_camera) {
warnings.push_back(RTR("XROrigin3D requires an XRCamera3D child node."));
}
}
bool xr_enabled = GLOBAL_GET("xr/shaders/enabled");
if (!xr_enabled) {
warnings.push_back(RTR("XR shaders are not enabled in project settings. Stereoscopic output is not supported unless they are enabled. Please enable `xr/shaders/enabled` to use stereoscopic output."));
}
return warnings;
}
void XROrigin3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_world_scale", "world_scale"), &XROrigin3D::set_world_scale);
ClassDB::bind_method(D_METHOD("get_world_scale"), &XROrigin3D::get_world_scale);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "world_scale"), "set_world_scale", "get_world_scale");
ClassDB::bind_method(D_METHOD("set_current", "enabled"), &XROrigin3D::set_current);
ClassDB::bind_method(D_METHOD("is_current"), &XROrigin3D::is_current);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current");
}
real_t XROrigin3D::get_world_scale() const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, 1.0);
return xr_server->get_world_scale();
}
void XROrigin3D::set_world_scale(real_t p_world_scale) {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
xr_server->set_world_scale(p_world_scale);
}
void XROrigin3D::_set_current(bool p_enabled, bool p_update_others) {
// We run this logic even if current already equals p_enabled as we may have set this previously before we entered our tree.
// This is then called a second time on NOTIFICATION_ENTER_TREE where we actually process activating this origin node.
current = p_enabled;
if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) {
return;
}
// Notify us of any transform changes
set_notify_local_transform(current);
set_notify_transform(current);
// update XRServer with our current position
if (current) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
xr_server->set_world_origin(get_global_transform());
if (is_physics_interpolated()) {
set_process_internal(true);
}
} else if (is_physics_interpolated()) {
set_process_internal(false);
}
// Check if we need to update our other origin nodes accordingly
if (p_update_others) {
if (current) {
for (int i = 0; i < origin_nodes.size(); i++) {
if (origin_nodes[i] != this && origin_nodes[i]->current) {
origin_nodes[i]->_set_current(false, false);
}
}
} else {
// We no longer have a current origin so find the first one we can make current
for (int i = 0; i < origin_nodes.size(); i++) {
if (origin_nodes[i] != this) {
origin_nodes[i]->_set_current(true, false);
return; // we are done.
}
}
}
}
}
void XROrigin3D::set_current(bool p_enabled) {
_set_current(p_enabled, true);
}
bool XROrigin3D::is_current() const {
if (Engine::get_singleton()->is_editor_hint()) {
// return as is
return current;
} else {
return current && is_inside_tree();
}
}
void XROrigin3D::_notification(int p_what) {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (!Engine::get_singleton()->is_editor_hint()) {
if (origin_nodes.is_empty()) {
// first entry always becomes current
current = true;
}
origin_nodes.push_back(this);
if (current) {
// set this again so we do whatever setup is needed.
set_current(true);
}
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (!Engine::get_singleton()->is_editor_hint()) {
origin_nodes.erase(this);
if (current) {
// We are no longer current
set_current(false);
}
}
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
case NOTIFICATION_TRANSFORM_CHANGED: {
if (current && !Engine::get_singleton()->is_editor_hint() && !is_physics_interpolated_and_enabled()) {
xr_server->set_world_origin(get_global_transform());
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (current && !Engine::get_singleton()->is_editor_hint() && is_physics_interpolated_and_enabled()) {
xr_server->set_world_origin(get_global_transform_interpolated());
}
} break;
}
if (current) {
// send our notification to all active XE interfaces, they may need to react to it also
for (int i = 0; i < xr_server->get_interface_count(); i++) {
Ref<XRInterface> interface = xr_server->get_interface(i);
if (interface.is_valid() && interface->is_initialized()) {
interface->notification(p_what);
}
}
}
}
void XROrigin3D::_physics_interpolated_changed() {
if (current && !Engine::get_singleton()->is_editor_hint()) {
set_process_internal(is_physics_interpolated());
}
}

206
scene/3d/xr/xr_nodes.h Normal file
View File

@@ -0,0 +1,206 @@
/**************************************************************************/
/* xr_nodes.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. */
/**************************************************************************/
#pragma once
#include "scene/3d/camera_3d.h"
#include "servers/xr/xr_positional_tracker.h"
/*
XRCamera is a subclass of camera which will register itself with its parent XROrigin and as a result is automatically positioned
*/
class XRCamera3D : public Camera3D {
GDCLASS(XRCamera3D, Camera3D);
protected:
// The name and pose for our HMD tracker is currently the only hardcoded bit.
// If we ever are able to support multiple HMDs we may need to make this settable.
StringName tracker_name = "head";
StringName pose_name = SceneStringName(default_);
Ref<XRPositionalTracker> tracker;
void _bind_tracker();
void _unbind_tracker();
void _changed_tracker(const StringName &p_tracker_name, int p_tracker_type);
void _removed_tracker(const StringName &p_tracker_name, int p_tracker_type);
void _pose_changed(const Ref<XRPose> &p_pose);
virtual void _physics_interpolated_changed() override;
public:
PackedStringArray get_configuration_warnings() const override;
virtual Vector3 project_local_ray_normal(const Point2 &p_pos) const override;
virtual Point2 unproject_position(const Vector3 &p_pos) const override;
virtual Vector3 project_position(const Point2 &p_point, real_t p_z_depth) const override;
virtual Vector<Plane> get_frustum() const override;
XRCamera3D();
~XRCamera3D();
};
/*
XRNode3D is a helper node that implements binding to a tracker.
It must be a child node of our XROrigin node
*/
class XRNode3D : public Node3D {
GDCLASS(XRNode3D, Node3D);
private:
StringName tracker_name;
StringName pose_name = SceneStringName(default_);
bool has_tracking_data = false;
bool show_when_tracked = false;
protected:
Ref<XRPositionalTracker> tracker;
static void _bind_methods();
virtual void _bind_tracker();
virtual void _unbind_tracker();
void _changed_tracker(const StringName &p_tracker_name, int p_tracker_type);
void _removed_tracker(const StringName &p_tracker_name, int p_tracker_type);
void _pose_changed(const Ref<XRPose> &p_pose);
void _pose_lost_tracking(const Ref<XRPose> &p_pose);
void _set_has_tracking_data(bool p_has_tracking_data);
void _update_visibility();
virtual void _physics_interpolated_changed() override;
public:
void _validate_property(PropertyInfo &p_property) const;
void set_tracker(const StringName &p_tracker_name);
StringName get_tracker() const;
void set_pose_name(const StringName &p_pose);
StringName get_pose_name() const;
bool get_is_active() const;
bool get_has_tracking_data() const;
void set_show_when_tracked(bool p_show);
bool get_show_when_tracked() const;
void trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0);
Ref<XRPose> get_pose();
PackedStringArray get_configuration_warnings() const override;
XRNode3D();
~XRNode3D();
};
/*
XRController3D is a helper node that automatically updates its position based on tracker data.
It must be a child node of our XROrigin node
*/
class XRController3D : public XRNode3D {
GDCLASS(XRController3D, XRNode3D);
private:
protected:
static void _bind_methods();
virtual void _bind_tracker() override;
virtual void _unbind_tracker() override;
void _button_pressed(const String &p_name);
void _button_released(const String &p_name);
void _input_float_changed(const String &p_name, float p_value);
void _input_vector2_changed(const String &p_name, Vector2 p_value);
void _profile_changed(const String &p_role);
public:
bool is_button_pressed(const StringName &p_name) const;
Variant get_input(const StringName &p_name) const;
float get_float(const StringName &p_name) const;
Vector2 get_vector2(const StringName &p_name) const;
XRPositionalTracker::TrackerHand get_tracker_hand() const;
};
/*
XRAnchor3D is a helper node that automatically updates its position based on anchor data, it represents a real world location.
It must be a child node of our XROrigin3D node
*/
class XRAnchor3D : public XRNode3D {
GDCLASS(XRAnchor3D, XRNode3D);
private:
Vector3 size;
protected:
static void _bind_methods();
public:
Vector3 get_size() const;
Plane get_plane() const;
};
/*
XROrigin3D is special spatial node that acts as our origin point mapping our real world center of our tracking volume into our virtual world.
It is this point that you will move around the world as the player 'moves while standing still', i.e. the player moves through teleporting or controller inputs as opposed to physically moving.
Our camera and controllers will always be child nodes and thus place relative to this origin point.
This node will automatically locate any camera child nodes and update its position while our XRController3D node will handle tracked controllers.
*/
class XROrigin3D : public Node3D {
GDCLASS(XROrigin3D, Node3D);
private:
bool current = false;
static Vector<XROrigin3D *> origin_nodes; // all origin nodes in tree
void _set_current(bool p_enabled, bool p_update_others);
protected:
void _notification(int p_what);
static void _bind_methods();
virtual void _physics_interpolated_changed() override;
public:
PackedStringArray get_configuration_warnings() const override;
real_t get_world_scale() const;
void set_world_scale(real_t p_world_scale);
void set_current(bool p_enabled);
bool is_current() const;
};