Merge pull request #97824 from TokageItLab/retarget-modifier

Add RetargetModifier3D for realtime retarget to keep original rest
This commit is contained in:
Thaddeus Crews 2024-11-26 13:04:47 -06:00
commit 04786f0ee8
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
18 changed files with 877 additions and 22 deletions

View File

@ -31,6 +31,12 @@
Returns the keys for the [Animation]s stored in the library.
</description>
</method>
<method name="get_animation_list_size" qualifiers="const">
<return type="int" />
<description>
Returns the key count for the [Animation]s stored in the library.
</description>
</method>
<method name="has_animation" qualifiers="const">
<return type="bool" />
<param index="0" name="name" type="StringName" />

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="RetargetModifier3D" inherits="SkeletonModifier3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A modifier to transfer parent skeleton poses (or global poses) to child skeletons in model space with different rests.
</brief_description>
<description>
Retrieves the pose (or global pose) relative to the parent Skeleton's rest in model space and transfers it to the child Skeleton.
This modifier rewrites the pose of the child skeleton directly in the parent skeleton's update process. This means that it overwrites the mapped bone pose set in the normal process on the target skeleton. If you want to set the target skeleton bone pose after retargeting, you will need to add a [SkeletonModifier3D] child to the target skeleton and thereby modify the pose.
[b]Note:[/b] When the [member use_global_pose] is enabled, even if it is an unmapped bone, it can cause visual problems because the global pose is applied ignoring the parent bone's pose [b]if it has mapped bone children[/b]. See also [member use_global_pose].
</description>
<tutorials>
</tutorials>
<members>
<member name="position_enabled" type="bool" setter="set_position_enabled" getter="is_position_enabled" default="true">
If [code]true[/code], allows to retarget the position.
</member>
<member name="profile" type="SkeletonProfile" setter="set_profile" getter="get_profile">
[SkeletonProfile] for retargeting bones with names matching the bone list.
</member>
<member name="rotation_enabled" type="bool" setter="set_rotation_enabled" getter="is_rotation_enabled" default="true">
If [code]true[/code], allows to retarget the rotation.
</member>
<member name="scale_enabled" type="bool" setter="set_scale_enabled" getter="is_scale_enabled" default="true">
If [code]true[/code], allows to retarget the scale.
</member>
<member name="use_global_pose" type="bool" setter="set_use_global_pose" getter="is_using_global_pose" default="false">
If [code]false[/code], in case the target skeleton has fewer bones than the source skeleton, the source bone parent's transform will be ignored.
Instead, it is possible to retarget between models with different body shapes, and position, rotation, and scale can be retargeted separately.
If [code]true[/code], retargeting is performed taking into account global pose.
In case the target skeleton has fewer bones than the source skeleton, the source bone parent's transform is taken into account. However, bone length between skeletons must match exactly, if not, the bones will be forced to expand or shrink.
This is useful for using dummy bone with length [code]0[/code] to match postures when retargeting between models with different number of bones.
</member>
</members>
</class>

View File

@ -393,6 +393,11 @@
[b]Note:[/b] During the update process, this signal is not fired, so modification by [SkeletonModifier3D] is not detected.
</description>
</signal>
<signal name="rest_updated">
<description>
Emitted when the rest is updated.
</description>
</signal>
<signal name="show_rest_only_changed">
<description>
Emitted when the value of [member show_rest_only] changes.

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#fc7f7f"><path d="m11.667 4.166h-3.334c-1.841 0-3.333 1.492-3.333 3.334.003 1.188.638 2.283 1.667 2.877v2.956c0 .597.317 1.146.833 1.444.254.146.54.221.833.221v.002h3.334v-.002c.293 0 .579-.075.833-.221.516-.299.833-.851.833-1.444v-2.956c1.028-.594 1.664-1.689 1.667-2.877 0-1.842-1.492-3.334-3.333-3.334zm-2.5 4.166h1.666v.834h-1.666zm-2.5-.832c0-.461.372-.834.833-.834s.833.373.833.834-.372.832-.833.832-.833-.371-.833-.832zm5.833 3.223v2.61h-.833v-.833h-.834v.833h-1.666v-.833h-.834v.833h-.833v-2.608-.725h.833v.832h.834v-.832h1.666v.832h.834v-.832h.833zm0-2.391c-.461 0-.833-.371-.833-.832s.372-.834.833-.834.833.373.833.834-.372.832-.833.832z"/><path d="m4.418 9.334h-.085v.833h-.833v-2.608-.725h.567c.323-2.072 2.104-3.668 4.266-3.668h2.445c-.473-1.263-1.682-2.166-3.111-2.166h-3.334c-1.841 0-3.333 1.492-3.333 3.334.003 1.188.638 2.283 1.667 2.877v2.956c0 .597.317 1.146.833 1.444.254.146.54.221.833.221v.002h1.334v-.929c-.538-.421-.962-.962-1.249-1.571zm-1.751-5c0-.461.372-.834.833-.834s.833.373.833.834-.372.832-.833.832-.833-.371-.833-.832z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -39,7 +39,7 @@
void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/unique_node/make_unique"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::STRING, "retarget/bone_renamer/unique_node/skeleton_name"), "GeneralSkeleton"));
}

View File

@ -33,6 +33,7 @@
#include "editor/import/3d/scene_import_settings.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/3d/retarget_modifier_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/bone_map.h"
@ -42,8 +43,18 @@ void PostImportPluginSkeletonRestFixer::get_internal_import_options(InternalImpo
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/apply_node_transforms"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/normalize_position_tracks"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/reset_all_bone_poses_after_import"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/overwrite_axis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "retarget/rest_fixer/retarget_method", PROPERTY_HINT_ENUM, "None,Overwrite Axis,Use Retarget Modifier", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/keep_global_rest_on_leftovers"), true));
String skeleton_bones_must_be_renamed_warning = String(
"The skeleton modifier option uses SkeletonProfile as a list of bone names and retargets by name matching. Without renaming, retargeting by modifier will not work and the track path of the animation will be broken and it will be not playbacked correctly."); // TODO: translate.
r_options->push_back(ResourceImporter::ImportOption(
PropertyInfo(
Variant::STRING, U"retarget/rest_fixer/\u26A0_validation_warning/skeleton_bones_must_be_renamed",
PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY),
Variant(skeleton_bones_must_be_renamed_warning)));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/use_global_pose"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::STRING, "retarget/rest_fixer/original_skeleton_name"), "OriginalSkeleton"));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/fix_silhouette/enable", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
// TODO: PostImportPlugin need to be implemented such as validate_option(PropertyInfo &property, const Dictionary &p_options).
// get_internal_option_visibility() is not sufficient because it can only retrieve options implemented in the core and can only read option values.
@ -63,7 +74,11 @@ Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(Intern
}
}
} else if (p_option == "retarget/rest_fixer/keep_global_rest_on_leftovers") {
return bool(p_options["retarget/rest_fixer/overwrite_axis"]);
return int(p_options["retarget/rest_fixer/retarget_method"]) == 1;
} else if (p_option == "retarget/rest_fixer/original_skeleton_name" || p_option == "retarget/rest_fixer/use_global_pose") {
return int(p_options["retarget/rest_fixer/retarget_method"]) == 2;
} else if (p_option.begins_with("retarget/") && p_option.ends_with("skeleton_bones_must_be_renamed")) {
return int(p_options["retarget/rest_fixer/retarget_method"]) == 2 && bool(p_options["retarget/bone_renamer/rename_bones"]) == false;
}
}
return true;
@ -147,7 +162,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
src_skeleton->set_bone_pose_position(src_idx, src_skeleton->get_bone_pose_position(src_idx) * scl);
}
// Fix animation.
// Fix animation by changing node transform.
bones_to_process = src_skeleton->get_parentless_bones();
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
@ -224,6 +239,10 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
if (String(name).contains("/")) {
continue; // Avoid animation library which may be created by importer dynamically.
}
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
@ -454,8 +473,13 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
}
// Overwrite axis.
if (bool(p_options["retarget/rest_fixer/overwrite_axis"])) {
bool is_using_modifier = int(p_options["retarget/rest_fixer/retarget_method"]) == 2;
bool is_using_global_pose = bool(p_options["retarget/rest_fixer/use_global_pose"]);
Skeleton3D *orig_skeleton = nullptr;
Skeleton3D *profile_skeleton = nullptr;
// Retarget in some way.
if (int(p_options["retarget/rest_fixer/retarget_method"]) > 0) {
LocalVector<Transform3D> old_skeleton_rest;
LocalVector<Transform3D> old_skeleton_global_rest;
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
@ -463,11 +487,151 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
old_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
}
// Build structure for modifier.
if (is_using_modifier) {
orig_skeleton = src_skeleton;
// Duplicate src_skeleton to modify animation tracks, it will memdelele after that animation track modification.
src_skeleton = memnew(Skeleton3D);
for (int i = 0; i < orig_skeleton->get_bone_count(); i++) {
src_skeleton->add_bone(orig_skeleton->get_bone_name(i));
src_skeleton->set_bone_rest(i, orig_skeleton->get_bone_rest(i));
src_skeleton->set_bone_pose(i, orig_skeleton->get_bone_pose(i));
}
for (int i = 0; i < orig_skeleton->get_bone_count(); i++) {
src_skeleton->set_bone_parent(i, orig_skeleton->get_bone_parent(i));
}
src_skeleton->set_motion_scale(orig_skeleton->get_motion_scale());
// Rename orig_skeleton (previous src_skeleton), since it is not animated by animation track with GeneralSkeleton.
String original_skeleton_name = String(p_options["retarget/rest_fixer/original_skeleton_name"]);
String skel_name = orig_skeleton->get_name();
ERR_FAIL_COND_MSG(original_skeleton_name.is_empty(), "Original skeleton name cannot be empty.");
ERR_FAIL_COND_MSG(original_skeleton_name == skel_name, "Original skeleton name must be different from unique skeleton name.");
// Rename profile skeleton to be general skeleton.
profile_skeleton = memnew(Skeleton3D);
bool is_unique = orig_skeleton->is_unique_name_in_owner();
if (is_unique) {
orig_skeleton->set_unique_name_in_owner(false);
}
orig_skeleton->set_name(original_skeleton_name);
profile_skeleton->set_name(skel_name);
if (is_unique) {
profile_skeleton->set_unique_name_in_owner(true);
}
// Build profile skeleton bones.
int len = profile->get_bone_size();
for (int i = 0; i < len; i++) {
profile_skeleton->add_bone(profile->get_bone_name(i));
profile_skeleton->set_bone_rest(i, profile->get_reference_pose(i));
}
for (int i = 0; i < len; i++) {
int target_parent = profile_skeleton->find_bone(profile->get_bone_parent(i));
if (target_parent >= 0) {
profile_skeleton->set_bone_parent(i, target_parent);
}
}
for (int i = 0; i < len; i++) {
Vector3 origin;
int found = orig_skeleton->find_bone(profile->get_bone_name(i));
String parent_name = profile->get_bone_parent(i);
if (found >= 0) {
origin = orig_skeleton->get_bone_global_rest(found).origin;
if (profile->get_bone_name(i) != profile->get_root_bone()) {
int src_parent = -1;
while (src_parent < 0 && !parent_name.is_empty()) {
src_parent = orig_skeleton->find_bone(parent_name);
parent_name = profile->get_bone_parent(profile->find_bone(parent_name));
}
if (src_parent >= 0) {
Transform3D parent_grest = orig_skeleton->get_bone_global_rest(src_parent);
origin = origin - parent_grest.origin;
}
}
}
int target_parent = profile_skeleton->find_bone(profile->get_bone_parent(i));
if (target_parent >= 0) {
origin = profile_skeleton->get_bone_global_rest(target_parent).basis.get_rotation_quaternion().xform_inv(origin);
}
profile_skeleton->set_bone_rest(i, Transform3D(profile_skeleton->get_bone_rest(i).basis, origin));
}
profile_skeleton->set_motion_scale(orig_skeleton->get_motion_scale());
profile_skeleton->reset_bone_poses();
// Make structure with modifier.
Node *owner = p_node->get_owner();
Node *pr = orig_skeleton->get_parent();
pr->add_child(profile_skeleton);
profile_skeleton->set_owner(owner);
RetargetModifier3D *mod = memnew(RetargetModifier3D);
profile_skeleton->add_child(mod);
mod->set_owner(owner);
mod->set_name("RetargetModifier3D");
orig_skeleton->set_owner(nullptr);
orig_skeleton->reparent(mod, false);
orig_skeleton->set_owner(owner);
orig_skeleton->set_unique_name_in_owner(true);
mod->set_use_global_pose(is_using_global_pose);
mod->set_profile(profile);
// Fix skeleton name in animation.
// Mapped skeleton is animated by %GenerarSkeleton:RenamedBoneName.
// Unmapped skeleton is animated by %OriginalSkeleton:OriginalBoneName.
if (is_using_modifier) {
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
String general_skeleton_pathname = UNIQUE_NODE_PREFIX + profile_skeleton->get_name();
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
if (anim->track_get_path(i).get_name_count() == 0) {
return;
}
if (anim->track_get_path(i).get_name(0) == general_skeleton_pathname) {
bool replace = false;
if (anim->track_get_path(i).get_subname_count() > 0) {
int found = profile_skeleton->find_bone(anim->track_get_path(i).get_concatenated_subnames());
if (found < 0) {
replace = true;
}
} else {
replace = true;
}
if (replace) {
String path_string = UNIQUE_NODE_PREFIX + original_skeleton_name;
if (anim->track_get_path(i).get_name_count() > 1) {
Vector<StringName> names = anim->track_get_path(i).get_names();
names.remove_at(0);
for (int j = 0; j < names.size(); j++) {
path_string += "/" + names[i].operator String();
}
}
if (anim->track_get_path(i).get_subname_count() > 0) {
path_string = path_string + String(":") + anim->track_get_path(i).get_concatenated_subnames();
}
anim->track_set_path(i, path_string);
}
}
}
}
}
}
}
bool keep_global_rest_leftovers = bool(p_options["retarget/rest_fixer/keep_global_rest_on_leftovers"]);
// Scan hierarchy and populate a whitelist of unmapped bones without mapped descendants.
// When both is_using_modifier and is_using_global_pose are enabled, this array is used for detecting warning.
Vector<int> keep_bone_rest;
if (keep_global_rest_leftovers) {
if (is_using_modifier || keep_global_rest_leftovers) {
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
while (bones_to_process.size() > 0) {
int src_idx = bones_to_process[0];
@ -526,12 +690,14 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
if (src_parent_idx >= 0) {
src_pg = src_skeleton->get_bone_global_rest(src_parent_idx).basis;
}
int prof_idx = profile->find_bone(src_bone_name);
if (prof_idx >= 0) {
tgt_rot = src_pg.inverse() * prof_skeleton->get_bone_global_rest(prof_idx).basis; // Mapped bone uses reference pose.
// Mapped bone uses reference pose.
// It is fine to change rest here even though is_using_modifier is enabled, since next process is aborted with unmapped bones.
tgt_rot = src_pg.inverse() * prof_skeleton->get_bone_global_rest(prof_idx).basis;
} else if (keep_global_rest_leftovers && keep_bone_rest.has(src_idx)) {
tgt_rot = src_pg.inverse() * old_skeleton_global_rest[src_idx].basis; // Non-Mapped bone without mapped children keeps global rest.
// Non-Mapped bones without mapped children keeps global rest.
tgt_rot = src_pg.inverse() * old_skeleton_global_rest[src_idx].basis;
}
}
@ -548,7 +714,8 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
src_skeleton->set_bone_rest(src_idx, Transform3D(tgt_rot, diff.xform(src_skeleton->get_bone_rest(src_idx).origin)));
}
// Fix animation.
// Fix animation by changing rest.
bool warning_detected = false;
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
@ -573,7 +740,9 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (!track_skeleton || track_skeleton != src_skeleton) {
if (!track_skeleton ||
(is_using_modifier && track_skeleton != profile_skeleton && track_skeleton != orig_skeleton) ||
(!is_using_modifier && track_skeleton != src_skeleton)) {
continue;
}
@ -584,6 +753,16 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
int bone_idx = src_skeleton->find_bone(bn);
if (is_using_modifier) {
int prof_idx = profile->find_bone(bn);
if (prof_idx < 0) {
if (keep_bone_rest.has(bone_idx)) {
warning_detected = true;
}
continue; // If is_using_modifier, the original skeleton rest is not changed.
}
}
Transform3D old_rest = old_skeleton_rest[bone_idx];
Transform3D new_rest = src_skeleton->get_bone_rest(bone_idx);
Transform3D old_pg;
@ -629,6 +808,13 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
}
}
if (is_using_global_pose && warning_detected) {
// TODO:
// Theoretically, if A and its conversion are calculated correctly taking into account the difference in the number of bones,
// there is no need to disable use_global_pose, but this is probably a fairly niche case.
WARN_PRINT_ED("Animated extra bone between mapped bones detected, consider disabling Use Global Pose option to prevent that the pose origin be overridden by the RetargetModifier3D.");
}
if (p_options.has("retarget/rest_fixer/reset_all_bone_poses_after_import") && !bool(p_options["retarget/rest_fixer/reset_all_bone_poses_after_import"])) {
// If Reset All Bone Poses After Import is disabled, preserve the original bone pose, adjusted for the new bone rolls.
for (int bone_idx = 0; bone_idx < src_skeleton->get_bone_count(); bone_idx++) {
@ -654,6 +840,11 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
}
if (is_using_modifier) {
memdelete(src_skeleton);
src_skeleton = profile_skeleton;
}
is_rest_changed = true;
}
@ -681,7 +872,9 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (!track_skeleton || track_skeleton != src_skeleton) {
if (!track_skeleton ||
(is_using_modifier && track_skeleton != profile_skeleton && track_skeleton != orig_skeleton) ||
(!is_using_modifier && track_skeleton != src_skeleton)) {
continue;
}
@ -696,7 +889,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
}
if (is_rest_changed) {
if (!is_using_modifier && is_rest_changed) {
// Fix skin.
{
HashSet<Ref<Skin>> mutated_skins;
@ -766,6 +959,14 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
src_skeleton->set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion());
src_skeleton->set_bone_pose_scale(i, fixed_rest.basis.get_scale());
}
if (orig_skeleton) {
for (int i = 0; i < orig_skeleton->get_bone_count(); i++) {
Transform3D fixed_rest = orig_skeleton->get_bone_rest(i);
orig_skeleton->set_bone_pose_position(i, fixed_rest.origin);
orig_skeleton->set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion());
orig_skeleton->set_bone_pose_scale(i, fixed_rest.basis.get_scale());
}
}
}
}

View File

@ -39,7 +39,7 @@ void PostImportPluginSkeletonTrackOrganizer::get_internal_import_options(Interna
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/except_bone_transform"), false));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unimportant_positions"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unmapped_bones"), false));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "retarget/remove_tracks/unmapped_bones", PROPERTY_HINT_ENUM, "None,Remove,Separate Library"), 0));
}
}
@ -61,9 +61,9 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate
}
bool remove_except_bone = bool(p_options["retarget/remove_tracks/except_bone_transform"]);
bool remove_positions = bool(p_options["retarget/remove_tracks/unimportant_positions"]);
bool remove_unmapped_bones = bool(p_options["retarget/remove_tracks/unmapped_bones"]);
int separate_unmapped_bones = int(p_options["retarget/remove_tracks/unmapped_bones"]);
if (!remove_positions && !remove_unmapped_bones) {
if (!remove_positions && separate_unmapped_bones == 0) {
return;
}
@ -72,10 +72,16 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
Ref<AnimationLibrary> unmapped_al;
unmapped_al.instantiate();
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
Vector<int> remove_indices;
Vector<int> mapped_bone_indices;
Vector<int> unmapped_bone_indices;
for (int i = 0; i < track_len; i++) {
String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
@ -96,16 +102,19 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate
StringName bn = anim->track_get_path(i).get_subname(0);
if (bn) {
int prof_idx = profile->find_bone(bone_map->find_profile_bone_name(bn));
if (remove_unmapped_bones && prof_idx < 0) {
remove_indices.push_back(i);
if (prof_idx < 0) {
unmapped_bone_indices.push_back(i);
continue;
}
if (remove_positions && anim->track_get_type(i) == Animation::TYPE_POSITION_3D && prof_idx >= 0) {
StringName prof_bn = profile->get_bone_name(prof_idx);
if (prof_bn == profile->get_root_bone() || prof_bn == profile->get_scale_base_bone()) {
mapped_bone_indices.push_back(i);
continue;
}
remove_indices.push_back(i);
} else {
mapped_bone_indices.push_back(i);
}
}
}
@ -114,11 +123,34 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate
}
}
if (separate_unmapped_bones == 2 && !unmapped_bone_indices.is_empty()) {
Ref<Animation> unmapped_anim = anim->duplicate();
Vector<int> to_delete;
to_delete.append_array(mapped_bone_indices);
to_delete.append_array(remove_indices);
to_delete.sort();
to_delete.reverse();
for (int E : to_delete) {
unmapped_anim->remove_track(E);
}
unmapped_al->add_animation(name, unmapped_anim);
}
if (separate_unmapped_bones >= 1) {
remove_indices.append_array(unmapped_bone_indices);
remove_indices.sort();
}
remove_indices.reverse();
for (int i = 0; i < remove_indices.size(); i++) {
anim->remove_track(remove_indices[i]);
}
}
if (unmapped_al->get_animation_list_size() == 0) {
unmapped_al.unref();
} else if (separate_unmapped_bones == 2) {
ap->add_animation_library("unmapped_bones", unmapped_al);
}
}
}
}

View File

@ -2312,6 +2312,7 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
}
}
// TODO: If there are more than 2 or equal get_internal_option_visibility method, visibility state is broken.
for (int i = 0; i < post_importer_plugins.size(); i++) {
Variant ret = post_importer_plugins.write[i]->get_internal_option_visibility(EditorScenePostImportPlugin::InternalImportCategory(p_category), _scene_import_type, p_option, p_options);
if (ret.get_type() == Variant::BOOL) {

View File

@ -841,10 +841,10 @@ void Node3D::reparent(Node *p_parent, bool p_keep_global_transform) {
ERR_THREAD_GUARD;
if (p_keep_global_transform) {
Transform3D temp = get_global_transform();
Node::reparent(p_parent);
Node::reparent(p_parent, p_keep_global_transform);
set_global_transform(temp);
} else {
Node::reparent(p_parent);
Node::reparent(p_parent, p_keep_global_transform);
}
}

View File

@ -0,0 +1,441 @@
/**************************************************************************/
/* retarget_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 "retarget_modifier_3d.h"
PackedStringArray RetargetModifier3D::get_configuration_warnings() const {
PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings();
if (child_skeletons.is_empty()) {
warnings.push_back(RTR("There is no child Skeleton3D!"));
}
return warnings;
}
/// Caching
void RetargetModifier3D::_profile_changed(Ref<SkeletonProfile> p_old, Ref<SkeletonProfile> p_new) {
if (p_old.is_valid() && p_old->is_connected(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset))) {
p_old->disconnect(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset));
}
profile = p_new;
if (p_new.is_valid() && !p_new->is_connected(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset))) {
p_new->connect(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset));
}
cache_rests_with_reset();
}
void RetargetModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) {
if (p_old && p_old->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests))) {
p_old->disconnect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests));
}
if (p_new && !p_new->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests))) {
p_new->connect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests));
}
cache_rests();
}
void RetargetModifier3D::cache_rests_with_reset() {
_reset_child_skeleton_poses();
cache_rests();
}
void RetargetModifier3D::cache_rests() {
source_bone_ids.clear();
Skeleton3D *source_skeleton = get_skeleton();
if (profile.is_null() || !source_skeleton) {
return;
}
PackedStringArray bone_names = profile->get_bone_names();
for (const String &E : bone_names) {
source_bone_ids.push_back(source_skeleton->find_bone(E));
}
for (int i = 0; i < child_skeletons.size(); i++) {
_update_child_skeleton_rests(i);
}
}
Vector<RetargetModifier3D::RetargetBoneInfo> RetargetModifier3D::cache_bone_global_rests(Skeleton3D *p_skeleton) {
// Retarget global pose in model space:
// tgt_global_pose.basis = src_global_pose.basis * src_rest.basis.inv * src_parent_global_rest.basis.inv * tgt_parent_global_rest.basis * tgt_rest.basis
// tgt_global_pose.origin = src_global_pose.origin
Skeleton3D *source_skeleton = get_skeleton();
Vector<RetargetBoneInfo> bone_rests;
if (profile.is_null() || !source_skeleton) {
return bone_rests;
}
PackedStringArray bone_names = profile->get_bone_names();
for (const String &E : bone_names) {
RetargetBoneInfo rbi;
int source_bone_id = source_skeleton->find_bone(E);
if (source_bone_id >= 0) {
Transform3D parent_global_rest;
int bone_parent = source_skeleton->get_bone_parent(source_bone_id);
if (bone_parent >= 0) {
parent_global_rest = source_skeleton->get_bone_global_rest(bone_parent);
}
rbi.post_basis = source_skeleton->get_bone_rest(source_bone_id).basis.inverse() * parent_global_rest.basis.inverse();
}
int target_bone_id = p_skeleton->find_bone(E);
rbi.bone_id = target_bone_id;
if (target_bone_id >= 0) {
Transform3D parent_global_rest;
int bone_parent = p_skeleton->get_bone_parent(target_bone_id);
if (bone_parent >= 0) {
parent_global_rest = p_skeleton->get_bone_global_rest(bone_parent);
}
rbi.post_basis = rbi.post_basis * parent_global_rest.basis * p_skeleton->get_bone_rest(target_bone_id).basis;
}
bone_rests.push_back(rbi);
}
return bone_rests;
}
Vector<RetargetModifier3D::RetargetBoneInfo> RetargetModifier3D::cache_bone_rests(Skeleton3D *p_skeleton) {
// Retarget pose in model space:
// tgt_pose.basis = tgt_parent_global_rest.basis.inv * src_parent_global_rest.basis * src_pose.basis * src_rest.basis.inv * src_parent_global_rest.basis.inv * tgt_parent_global_rest.basis * tgt_rest.basis
// tgt_pose.origin = tgt_parent_global_rest.basis.inv.xform(src_parent_global_rest.basis.xform(src_pose.origin - src_rest.origin)) + tgt_rest.origin
Skeleton3D *source_skeleton = get_skeleton();
Vector<RetargetBoneInfo> bone_rests;
if (profile.is_null() || !source_skeleton) {
return bone_rests;
}
PackedStringArray bone_names = profile->get_bone_names();
for (const String &E : bone_names) {
RetargetBoneInfo rbi;
int source_bone_id = source_skeleton->find_bone(E);
if (source_bone_id >= 0) {
Transform3D parent_global_rest;
int bone_parent = source_skeleton->get_bone_parent(source_bone_id);
if (bone_parent >= 0) {
parent_global_rest = source_skeleton->get_bone_global_rest(bone_parent);
}
rbi.pre_basis = parent_global_rest.basis;
rbi.post_basis = source_skeleton->get_bone_rest(source_bone_id).basis.inverse() * parent_global_rest.basis.inverse();
}
int target_bone_id = p_skeleton->find_bone(E);
rbi.bone_id = target_bone_id;
if (target_bone_id >= 0) {
Transform3D parent_global_rest;
int bone_parent = p_skeleton->get_bone_parent(target_bone_id);
if (bone_parent >= 0) {
parent_global_rest = p_skeleton->get_bone_global_rest(bone_parent);
}
rbi.pre_basis = parent_global_rest.basis.inverse() * rbi.pre_basis;
rbi.post_basis = rbi.post_basis * parent_global_rest.basis * p_skeleton->get_bone_rest(target_bone_id).basis;
}
bone_rests.push_back(rbi);
}
return bone_rests;
}
void RetargetModifier3D::_update_child_skeleton_rests(int p_child_skeleton_idx) {
ERR_FAIL_INDEX(p_child_skeleton_idx, child_skeletons.size());
Skeleton3D *c = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(child_skeletons[p_child_skeleton_idx].skeleton_id));
if (!c) {
return;
}
if (use_global_pose) {
child_skeletons.write[p_child_skeleton_idx].humanoid_bone_rests = cache_bone_global_rests(c);
} else {
child_skeletons.write[p_child_skeleton_idx].humanoid_bone_rests = cache_bone_rests(c);
}
}
void RetargetModifier3D::_update_child_skeletons() {
_reset_child_skeletons();
for (int i = 0; i < get_child_count(); i++) {
RetargetInfo ri;
Skeleton3D *c = Object::cast_to<Skeleton3D>(get_child(i));
if (c) {
int id = child_skeletons.size();
ri.skeleton_id = c->get_instance_id();
child_skeletons.push_back(ri);
c->connect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests).bind(id));
}
}
cache_rests();
update_configuration_warnings();
}
void RetargetModifier3D::_reset_child_skeleton_poses() {
for (const RetargetInfo &E : child_skeletons) {
Skeleton3D *c = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id));
if (!c) {
continue;
}
if (c->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests))) {
c->disconnect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests));
}
for (const RetargetBoneInfo &F : E.humanoid_bone_rests) {
if (F.bone_id < 0) {
continue;
}
c->reset_bone_pose(F.bone_id);
}
}
}
void RetargetModifier3D::_reset_child_skeletons() {
_reset_child_skeleton_poses();
child_skeletons.clear();
}
/// General functions
void RetargetModifier3D::add_child_notify(Node *p_child) {
if (Object::cast_to<Skeleton3D>(p_child)) {
_update_child_skeletons();
}
}
void RetargetModifier3D::move_child_notify(Node *p_child) {
if (Object::cast_to<Skeleton3D>(p_child)) {
_update_child_skeletons();
}
}
void RetargetModifier3D::remove_child_notify(Node *p_child) {
if (Object::cast_to<Skeleton3D>(p_child)) {
// Reset after process.
callable_mp(this, &RetargetModifier3D::_update_child_skeletons).call_deferred();
}
}
void RetargetModifier3D::_validate_property(PropertyInfo &p_property) const {
if (use_global_pose) {
if (p_property.name == "position_enabled" || p_property.name == "rotation_enabled" || p_property.name == "scale_enabled") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
}
void RetargetModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_profile", "profile"), &RetargetModifier3D::set_profile);
ClassDB::bind_method(D_METHOD("get_profile"), &RetargetModifier3D::get_profile);
ClassDB::bind_method(D_METHOD("set_use_global_pose", "use_global_pose"), &RetargetModifier3D::set_use_global_pose);
ClassDB::bind_method(D_METHOD("is_using_global_pose"), &RetargetModifier3D::is_using_global_pose);
ClassDB::bind_method(D_METHOD("set_position_enabled", "enabled"), &RetargetModifier3D::set_position_enabled);
ClassDB::bind_method(D_METHOD("is_position_enabled"), &RetargetModifier3D::is_position_enabled);
ClassDB::bind_method(D_METHOD("set_rotation_enabled", "enabled"), &RetargetModifier3D::set_rotation_enabled);
ClassDB::bind_method(D_METHOD("is_rotation_enabled"), &RetargetModifier3D::is_rotation_enabled);
ClassDB::bind_method(D_METHOD("set_scale_enabled", "enabled"), &RetargetModifier3D::set_scale_enabled);
ClassDB::bind_method(D_METHOD("is_scale_enabled"), &RetargetModifier3D::is_scale_enabled);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "profile", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonProfile"), "set_profile", "get_profile");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_pose"), "set_use_global_pose", "is_using_global_pose");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "position_enabled"), "set_position_enabled", "is_position_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotation_enabled"), "set_rotation_enabled", "is_rotation_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scale_enabled"), "set_scale_enabled", "is_scale_enabled");
}
void RetargetModifier3D::_set_active(bool p_active) {
if (!p_active) {
_reset_child_skeleton_poses();
}
}
void RetargetModifier3D::_retarget_global_pose() {
Skeleton3D *source_skeleton = get_skeleton();
if (profile.is_null() || !source_skeleton) {
return;
}
LocalVector<Transform3D> source_poses;
if (influence < 1.0) {
for (int source_bone_id : source_bone_ids) {
source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_global_rest(source_bone_id).interpolate_with(source_skeleton->get_bone_global_pose(source_bone_id), influence));
}
} else {
for (int source_bone_id : source_bone_ids) {
source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_global_pose(source_bone_id));
}
}
for (const RetargetInfo &E : child_skeletons) {
Skeleton3D *target_skeleton = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id));
if (!target_skeleton) {
continue;
}
for (int i = 0; i < source_bone_ids.size(); i++) {
int target_bone_id = E.humanoid_bone_rests[i].bone_id;
if (target_bone_id < 0) {
continue;
}
Transform3D retarget_pose = source_poses[i];
retarget_pose.basis = retarget_pose.basis * E.humanoid_bone_rests[i].post_basis;
target_skeleton->set_bone_global_pose(target_bone_id, retarget_pose);
}
}
}
void RetargetModifier3D::_retarget_pose() {
Skeleton3D *source_skeleton = get_skeleton();
if (profile.is_null() || !source_skeleton) {
return;
}
LocalVector<Transform3D> source_poses;
if (influence < 1.0) {
for (int source_bone_id : source_bone_ids) {
source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_rest(source_bone_id).interpolate_with(source_skeleton->get_bone_pose(source_bone_id), influence));
}
} else {
for (int source_bone_id : source_bone_ids) {
source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_pose(source_bone_id));
}
}
for (const RetargetInfo &E : child_skeletons) {
Skeleton3D *target_skeleton = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id));
if (!target_skeleton) {
continue;
}
float motion_scale_ratio = target_skeleton->get_motion_scale() / source_skeleton->get_motion_scale();
for (int i = 0; i < source_bone_ids.size(); i++) {
int target_bone_id = E.humanoid_bone_rests[i].bone_id;
if (target_bone_id < 0) {
continue;
}
int source_bone_id = source_bone_ids[i];
if (source_bone_id < 0) {
continue;
}
Transform3D extracted_transform = source_poses[i];
extracted_transform.basis = E.humanoid_bone_rests[i].pre_basis * extracted_transform.basis * E.humanoid_bone_rests[i].post_basis;
extracted_transform.origin = E.humanoid_bone_rests[i].pre_basis.xform((extracted_transform.origin - source_skeleton->get_bone_rest(source_bone_id).origin) * motion_scale_ratio) + target_skeleton->get_bone_rest(target_bone_id).origin;
Transform3D retarget_pose = target_skeleton->get_bone_pose(target_bone_id);
if (enable_position) {
retarget_pose.origin = extracted_transform.origin;
}
if (enable_rotation) {
retarget_pose.basis = extracted_transform.basis.get_rotation_quaternion();
}
if (enable_scale) {
retarget_pose.basis.scale_local(extracted_transform.basis.get_scale());
}
target_skeleton->set_bone_pose(target_bone_id, retarget_pose);
}
}
}
void RetargetModifier3D::_process_modification() {
if (use_global_pose) {
_retarget_global_pose();
} else {
_retarget_pose();
}
}
void RetargetModifier3D::set_profile(Ref<SkeletonProfile> p_profile) {
if (profile == p_profile) {
return;
}
_profile_changed(profile, p_profile);
}
Ref<SkeletonProfile> RetargetModifier3D::get_profile() const {
return profile;
}
void RetargetModifier3D::set_use_global_pose(bool p_use_global_pose) {
if (use_global_pose == p_use_global_pose) {
return;
}
use_global_pose = p_use_global_pose;
cache_rests_with_reset();
notify_property_list_changed();
}
bool RetargetModifier3D::is_using_global_pose() const {
return use_global_pose;
}
void RetargetModifier3D::set_position_enabled(bool p_enabled) {
if (enable_position != p_enabled) {
_reset_child_skeleton_poses();
}
enable_position = p_enabled;
}
bool RetargetModifier3D::is_position_enabled() const {
return enable_position;
}
void RetargetModifier3D::set_rotation_enabled(bool p_enabled) {
if (enable_rotation != p_enabled) {
_reset_child_skeleton_poses();
}
enable_rotation = p_enabled;
}
bool RetargetModifier3D::is_rotation_enabled() const {
return enable_rotation;
}
void RetargetModifier3D::set_scale_enabled(bool p_enabled) {
if (enable_scale != p_enabled) {
_reset_child_skeleton_poses();
}
enable_scale = p_enabled;
}
bool RetargetModifier3D::is_scale_enabled() const {
return enable_scale;
}
void RetargetModifier3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_update_child_skeletons();
} break;
case NOTIFICATION_EDITOR_PRE_SAVE: {
_reset_child_skeleton_poses();
} break;
case NOTIFICATION_EXIT_TREE: {
_reset_child_skeletons();
} break;
}
}
RetargetModifier3D::RetargetModifier3D() {
}
RetargetModifier3D::~RetargetModifier3D() {
}

View File

@ -0,0 +1,110 @@
/**************************************************************************/
/* retarget_modifier_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef RETARGET_MODIFIER_3D_H
#define RETARGET_MODIFIER_3D_H
#include "scene/3d/skeleton_modifier_3d.h"
#include "scene/resources/skeleton_profile.h"
class RetargetModifier3D : public SkeletonModifier3D {
GDCLASS(RetargetModifier3D, SkeletonModifier3D);
Ref<SkeletonProfile> profile;
bool use_global_pose = false;
bool enable_position = true;
bool enable_rotation = true;
bool enable_scale = true;
struct RetargetBoneInfo {
int bone_id = -1;
Basis pre_basis;
Basis post_basis;
};
struct RetargetInfo {
ObjectID skeleton_id;
Vector<RetargetBoneInfo> humanoid_bone_rests;
};
Vector<RetargetInfo> child_skeletons;
Vector<int> source_bone_ids;
void _update_child_skeleton_rests(int p_child_skeleton_idx);
void _update_child_skeletons();
void _reset_child_skeleton_poses();
void _reset_child_skeletons();
void cache_rests_with_reset();
void cache_rests();
Vector<RetargetBoneInfo> cache_bone_global_rests(Skeleton3D *p_skeleton);
Vector<RetargetBoneInfo> cache_bone_rests(Skeleton3D *p_skeleton);
Vector<RetargetBoneInfo> get_humanoid_bone_rests(Skeleton3D *p_skeleton);
void _retarget_global_pose();
void _retarget_pose();
protected:
virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override;
void _profile_changed(Ref<SkeletonProfile> p_old, Ref<SkeletonProfile> p_new);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
virtual void _notification(int p_what);
virtual void add_child_notify(Node *p_child) override;
virtual void move_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
virtual void _set_active(bool p_active) override;
virtual void _process_modification() override;
public:
virtual PackedStringArray get_configuration_warnings() const override;
void set_use_global_pose(bool p_use_global_pose);
bool is_using_global_pose() const;
void set_position_enabled(bool p_enabled);
bool is_position_enabled() const;
void set_rotation_enabled(bool p_enabled);
bool is_rotation_enabled() const;
void set_scale_enabled(bool p_enabled);
bool is_scale_enabled() const;
void set_profile(Ref<SkeletonProfile> p_profile);
Ref<SkeletonProfile> get_profile() const;
RetargetModifier3D();
virtual ~RetargetModifier3D();
};
#endif // RETARGET_MODIFIER_3D_H

View File

@ -1048,7 +1048,12 @@ void Skeleton3D::force_update_all_bone_transforms() {
for (int i = 0; i < parentless_bones.size(); i++) {
force_update_bone_children_transforms(parentless_bones[i]);
}
rest_dirty = false;
if (rest_dirty) {
rest_dirty = false;
emit_signal(SNAME("rest_updated"));
} else {
rest_dirty = false;
}
dirty = false;
if (updating) {
return;
@ -1258,6 +1263,7 @@ void Skeleton3D::_bind_methods() {
ADD_GROUP("Modifier", "modifier_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "modifier_callback_mode_process", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_modifier_callback_mode_process", "get_modifier_callback_mode_process");
ADD_SIGNAL(MethodInfo("rest_updated"));
ADD_SIGNAL(MethodInfo("pose_updated"));
ADD_SIGNAL(MethodInfo("skeleton_updated"));
ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx")));

View File

@ -222,6 +222,7 @@ public:
// Skeleton creation API
uint64_t get_version() const;
int add_bone(const String &p_name);
void remove_bone(int p_bone);
int find_bone(const String &p_name) const;
String get_bone_name(int p_bone) const;
void set_bone_name(int p_bone, const String &p_name);

View File

@ -277,6 +277,7 @@
#include "scene/3d/physics/vehicle_body_3d.h"
#include "scene/3d/reflection_probe.h"
#include "scene/3d/remote_transform_3d.h"
#include "scene/3d/retarget_modifier_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/3d/skeleton_ik_3d.h"
#include "scene/3d/skeleton_modifier_3d.h"
@ -594,6 +595,7 @@ void register_scene_types() {
GDREGISTER_CLASS(Marker3D);
GDREGISTER_CLASS(RootMotionView);
GDREGISTER_VIRTUAL_CLASS(SkeletonModifier3D);
GDREGISTER_CLASS(RetargetModifier3D);
OS::get_singleton()->yield(); // may take time to init

View File

@ -125,6 +125,10 @@ void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const
}
}
int AnimationLibrary::get_animation_list_size() const {
return animations.size();
}
void AnimationLibrary::_set_data(const Dictionary &p_data) {
for (KeyValue<StringName, Ref<Animation>> &K : animations) {
K.value->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed));
@ -166,6 +170,7 @@ void AnimationLibrary::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationLibrary::has_animation);
ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationLibrary::get_animation);
ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationLibrary::_get_animation_list);
ClassDB::bind_method(D_METHOD("get_animation_list_size"), &AnimationLibrary::get_animation_list_size);
ClassDB::bind_method(D_METHOD("_set_data", "data"), &AnimationLibrary::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &AnimationLibrary::_get_data);

View File

@ -61,6 +61,7 @@ public:
bool has_animation(const StringName &p_name) const;
Ref<Animation> get_animation(const StringName &p_name) const;
void get_animation_list(List<StringName> *p_animations) const;
int get_animation_list_size() const;
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;

View File

@ -269,6 +269,14 @@ int SkeletonProfile::find_bone(const StringName &p_bone_name) const {
return -1;
}
PackedStringArray SkeletonProfile::get_bone_names() {
PackedStringArray s;
for (const SkeletonProfileBone &bone : bones) {
s.push_back(bone.bone_name);
}
return s;
}
StringName SkeletonProfile::get_bone_name(int p_bone_idx) const {
ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName());
return bones[p_bone_idx].bone_name;

View File

@ -97,6 +97,7 @@ public:
int find_bone(const StringName &p_bone_name) const;
PackedStringArray get_bone_names();
StringName get_bone_name(int p_bone_idx) const;
void set_bone_name(int p_bone_idx, const StringName &p_bone_name);