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
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:
6
scene/3d/xr/SCsub
Normal file
6
scene/3d/xr/SCsub
Normal 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")
|
363
scene/3d/xr/xr_body_modifier_3d.cpp
Normal file
363
scene/3d/xr/xr_body_modifier_3d.cpp
Normal 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;
|
||||
}
|
||||
}
|
92
scene/3d/xr/xr_body_modifier_3d.h
Normal file
92
scene/3d/xr/xr_body_modifier_3d.h
Normal 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)
|
615
scene/3d/xr/xr_face_modifier_3d.cpp
Normal file
615
scene/3d/xr/xr_face_modifier_3d.cpp
Normal 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;
|
||||
}
|
||||
}
|
70
scene/3d/xr/xr_face_modifier_3d.h
Normal file
70
scene/3d/xr/xr_face_modifier_3d.h
Normal 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);
|
||||
};
|
325
scene/3d/xr/xr_hand_modifier_3d.cpp
Normal file
325
scene/3d/xr/xr_hand_modifier_3d.cpp
Normal 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;
|
||||
}
|
||||
}
|
84
scene/3d/xr/xr_hand_modifier_3d.h
Normal file
84
scene/3d/xr/xr_hand_modifier_3d.h
Normal 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
846
scene/3d/xr/xr_nodes.cpp
Normal 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
206
scene/3d/xr/xr_nodes.h
Normal 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;
|
||||
};
|
Reference in New Issue
Block a user