Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add position track normalization to importer retarget #63138

Merged
merged 1 commit into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/classes/Skeleton3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,10 @@
<members>
<member name="animate_physical_bones" type="bool" setter="set_animate_physical_bones" getter="get_animate_physical_bones" default="true">
</member>
<member name="motion_scale" type="float" setter="set_motion_scale" getter="get_motion_scale" default="1.0">
Multiplies the position 3D track animation.
[b]Note:[/b] Unless this value is [code]1.0[/code], the key value in animation will not match the actual position value.
</member>
<member name="show_rest_only" type="bool" setter="set_show_rest_only" getter="is_show_rest_only" default="false">
</member>
</members>
Expand Down
8 changes: 8 additions & 0 deletions doc/classes/SkeletonProfile.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@
</member>
<member name="group_size" type="int" setter="set_group_size" getter="get_group_size" default="0">
</member>
<member name="root_bone" type="StringName" setter="set_root_bone" getter="get_root_bone" default="&amp;&quot;&quot;">
A name of bone that will be used as the root bone in [AnimationTree].
[b]Note:[/b] In most cases, it is the bone of the parent of the hips that exists at the world origin in the humanoid model.
</member>
<member name="scale_base_bone" type="StringName" setter="set_scale_base_bone" getter="get_scale_base_bone" default="&amp;&quot;&quot;">
A name of bone which height will be used as the coefficient for normalization.
[b]Note:[/b] In most cases, it is hips in the humanoid model.
</member>
</members>
<signals>
<signal name="profile_updated">
Expand Down
2 changes: 2 additions & 0 deletions doc/classes/SkeletonProfileHumanoid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@
<members>
<member name="bone_size" type="int" setter="set_bone_size" getter="get_bone_size" overrides="SkeletonProfile" default="56" />
<member name="group_size" type="int" setter="set_group_size" getter="get_group_size" overrides="SkeletonProfile" default="4" />
<member name="root_bone" type="StringName" setter="set_root_bone" getter="get_root_bone" overrides="SkeletonProfile" default="&amp;&quot;Root&quot;" />
<member name="scale_base_bone" type="StringName" setter="set_scale_base_bone" getter="get_scale_base_bone" overrides="SkeletonProfile" default="&amp;&quot;Hips&quot;" />
</members>
</class>
95 changes: 83 additions & 12 deletions editor/import/post_import_plugin_skeleton_rest_fixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/animation.h"
#include "scene/resources/bone_map.h"

void PostImportPluginSkeletonRestFixer::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/rest_fixer/normalize_position_tracks"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/overwrite_axis"), true));

r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/fix_silhouette/enable"), false));
Expand Down Expand Up @@ -89,6 +89,53 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
}

// Set motion scale to Skeleton if normalize position tracks.
if (bool(p_options["retarget/rest_fixer/normalize_position_tracks"])) {
int src_bone_idx = src_skeleton->find_bone(profile->get_scale_base_bone());
if (src_bone_idx >= 0) {
real_t motion_scale = abs(src_skeleton->get_bone_global_rest(src_bone_idx).origin.y);
if (motion_scale > 0) {
src_skeleton->set_motion_scale(motion_scale);
}
}

TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
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_subname_count() != 1 || anim->track_get_type(i) != Animation::TYPE_POSITION_3D) {
continue;
}

if (anim->track_is_compressed(i)) {
continue; // Shouldn't occur in internal_process().
}

String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
if (node) {
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (track_skeleton) {
if (track_skeleton && track_skeleton == src_skeleton) {
real_t mlt = 1 / src_skeleton->get_motion_scale();
int key_len = anim->track_get_key_count(i);
for (int j = 0; j < key_len; j++) {
Vector3 pos = static_cast<Vector3>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, pos * mlt);
}
}
}
}
}
}
}
}

// Complement Rotation track for compatibility between different rests.
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
Expand Down Expand Up @@ -357,12 +404,12 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
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_subname_count() != 1 || anim->track_get_type(i) != Animation::TYPE_ROTATION_3D) {
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
continue;
}

if (anim->track_is_compressed(i)) {
continue; // TODO: Adopt to compressed track.
continue; // Shouldn't occur in internal_process().
}

String track_path = String(anim->track_get_path(i).get_concatenated_names());
Expand All @@ -374,20 +421,44 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
if (bn) {
int bone_idx = src_skeleton->find_bone(bn);

Quaternion old_rest = old_skeleton_rest[bone_idx].basis.get_rotation_quaternion();
Quaternion new_rest = src_skeleton->get_bone_rest(bone_idx).basis.get_rotation_quaternion();
Quaternion old_pg;
Quaternion new_pg;
Transform3D old_rest = old_skeleton_rest[bone_idx];
Transform3D new_rest = src_skeleton->get_bone_rest(bone_idx);
Transform3D old_pg;
Transform3D new_pg;
int parent_idx = src_skeleton->get_bone_parent(bone_idx);
if (parent_idx >= 0) {
old_pg = old_skeleton_global_rest[parent_idx].basis.get_rotation_quaternion();
new_pg = src_skeleton->get_bone_global_rest(parent_idx).basis.get_rotation_quaternion();
old_pg = old_skeleton_global_rest[parent_idx];
new_pg = src_skeleton->get_bone_global_rest(parent_idx);
}

int key_len = anim->track_get_key_count(i);
for (int j = 0; j < key_len; j++) {
Quaternion qt = static_cast<Quaternion>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, new_pg.inverse() * old_pg * qt * old_rest.inverse() * old_pg.inverse() * new_pg * new_rest);
if (anim->track_get_type(i) == Animation::TYPE_ROTATION_3D) {
Quaternion old_rest_q = old_rest.basis.get_rotation_quaternion();
Quaternion new_rest_q = new_rest.basis.get_rotation_quaternion();
Quaternion old_pg_q = old_pg.basis.get_rotation_quaternion();
Quaternion new_pg_q = new_pg.basis.get_rotation_quaternion();
for (int j = 0; j < key_len; j++) {
Quaternion qt = static_cast<Quaternion>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, new_pg_q.inverse() * old_pg_q * qt * old_rest_q.inverse() * old_pg_q.inverse() * new_pg_q * new_rest_q);
}
} else if (anim->track_get_type(i) == Animation::TYPE_SCALE_3D) {
Basis old_rest_b = old_rest.basis;
Basis new_rest_b = new_rest.basis;
Basis old_pg_b = old_pg.basis;
Basis new_pg_b = new_pg.basis;
for (int j = 0; j < key_len; j++) {
Basis sc = Basis().scaled(static_cast<Vector3>(anim->track_get_key_value(i, j)));
anim->track_set_key_value(i, j, (new_pg_b.inverse() * old_pg_b * sc * old_rest_b.inverse() * old_pg_b.inverse() * new_pg_b * new_rest_b).get_scale());
}
} else {
Vector3 old_rest_o = old_rest.origin;
Vector3 new_rest_o = new_rest.origin;
Quaternion old_pg_q = old_pg.basis.get_rotation_quaternion();
Quaternion new_pg_q = new_pg.basis.get_rotation_quaternion();
for (int j = 0; j < key_len; j++) {
Vector3 ps = static_cast<Vector3>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, new_pg_q.xform_inv(old_pg_q.xform(ps - old_rest_o)) + new_rest_o);
}
}
}
}
Expand Down
116 changes: 116 additions & 0 deletions editor/import/post_import_plugin_skeleton_track_organizer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*************************************************************************/
/* post_import_plugin_skeleton_track_organizer.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "post_import_plugin_skeleton_track_organizer.h"

#include "editor/import/scene_import_settings.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/bone_map.h"

void PostImportPluginSkeletonTrackOrganizer::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/remove_tracks/unimportant_positions"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unmapped_bones"), false));
}
}

void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
// Prepare objects.
Object *map = p_options["retarget/bone_map"].get_validated_object();
if (!map) {
return;
}
BoneMap *bone_map = Object::cast_to<BoneMap>(map);
Ref<SkeletonProfile> profile = bone_map->get_profile();
if (!profile.is_valid()) {
return;
}
Skeleton3D *src_skeleton = Object::cast_to<Skeleton3D>(p_node);
if (!src_skeleton) {
return;
}
bool remove_positions = bool(p_options["retarget/remove_tracks/unimportant_positions"]);
bool remove_unmapped_bones = bool(p_options["retarget/remove_tracks/unmapped_bones"]);

if (!remove_positions && !remove_unmapped_bones) {
return;
}

TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
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();
Vector<int> remove_indices;
for (int i = 0; i < track_len; i++) {
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
continue;
}

String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
if (node) {
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (track_skeleton && track_skeleton == src_skeleton) {
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);
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()) {
continue;
}
remove_indices.push_back(i);
}
}
}
}
}

remove_indices.reverse();
for (int i = 0; i < remove_indices.size(); i++) {
anim->remove_track(remove_indices[i]);
}
}
}
}
}

PostImportPluginSkeletonTrackOrganizer::PostImportPluginSkeletonTrackOrganizer() {
}
46 changes: 46 additions & 0 deletions editor/import/post_import_plugin_skeleton_track_organizer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*************************************************************************/
/* post_import_plugin_skeleton_track_organizer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H
#define POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H

#include "resource_importer_scene.h"

class PostImportPluginSkeletonTrackOrganizer : public EditorScenePostImportPlugin {
GDCLASS(PostImportPluginSkeletonTrackOrganizer, EditorScenePostImportPlugin);

public:
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;

PostImportPluginSkeletonTrackOrganizer();
};

#endif // POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H
Loading