From f0d99e2c7b878bb53ab0ba0a1db2fe4311bf4903 Mon Sep 17 00:00:00 2001 From: "K. S. Ernest (iFire) Lee" Date: Wed, 13 Oct 2021 05:03:21 -0700 Subject: [PATCH] Bake reset animation to special VRM-like bone rest. Point bone parent to children. Leaf bones point to parent. Co-authored-by: MMMaellon Co-authored-by: Eron --- editor/import/editor_importer_bake_reset.cpp | 270 ++++++++++++++++ editor/import/editor_importer_bake_reset.h | 55 ++++ .../editor_importer_point_parent_to_child.cpp | 301 ++++++++++++++++++ .../editor_importer_point_parent_to_child.h | 61 ++++ editor/import/resource_importer_scene.cpp | 18 +- 5 files changed, 704 insertions(+), 1 deletion(-) create mode 100644 editor/import/editor_importer_bake_reset.cpp create mode 100644 editor/import/editor_importer_bake_reset.h create mode 100644 editor/import/editor_importer_point_parent_to_child.cpp create mode 100644 editor/import/editor_importer_point_parent_to_child.h diff --git a/editor/import/editor_importer_bake_reset.cpp b/editor/import/editor_importer_bake_reset.cpp new file mode 100644 index 000000000000..6425cd17b1ec --- /dev/null +++ b/editor/import/editor_importer_bake_reset.cpp @@ -0,0 +1,270 @@ +/*************************************************************************/ +/* editor_importer_bake_reset.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "editor/import/editor_importer_bake_reset.h" + +#include "core/error/error_list.h" +#include "core/error/error_macros.h" +#include "core/math/transform_3d.h" +#include "core/string/ustring.h" +#include "resource_importer_scene.h" +#include "scene/3d/importer_mesh_instance_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/animation/animation_player.h" + +void BakeReset::_bake_animation_pose(Node *scene, const String &p_bake_anim) { + Map r_rest_bones; + Vector r_meshes; + List queue; + queue.push_back(scene); + while (!queue.is_empty()) { + List::Element *E = queue.front(); + Node *node = E->get(); + ImporterMeshInstance3D *editor_mesh_3d = scene->cast_to(node); + MeshInstance3D *mesh_3d = scene->cast_to(node); + if (scene->cast_to(node)) { + Skeleton3D *skeleton = Object::cast_to(node); + _transform_bone(skeleton, "hips", Basis(), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "spine", Basis(), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "chest", Basis(), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "spine", Basis(), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "upperChest", Basis(), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "neck", Basis(), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "head", Basis(), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "spine", Basis(), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftUpperLeg", Basis(Vector3(-1.0, 0.0, 0.0), Vector3(0.0, -1.0, 0.0), Vector3(0.0, 0.0, 1.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftLowerLeg", Basis(Vector3(1.0, 0.0, 0.0), Vector3(0, -1.0, 0.0), Vector3(0.0, 0.0, -1.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftFoot", Basis(Vector3(1.0, 0.0, 0.0), Vector3(0, -1.0, 0.0), Vector3(0.0, 0.0, -1.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftLowerLeg", Basis(Vector3(-1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0), Vector3(0, 1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftToes", Basis(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0), Vector3(0, -1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightUpperLeg", Basis(Vector3(-1.0, 0.0, 0.0), Vector3(0, -1.0, 0.0), Vector3(0.0, 0.0, 1.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightLowerLeg", Basis(Vector3(1.0, 0.0, 0.0), Vector3(0, -1.0, 0.0), Vector3(0.0, 0.0, -1.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightFoot", Basis(Vector3(-1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0), Vector3(0, 1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightToes", Basis(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0), Vector3(0, -1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftShoulder", Basis(Vector3(0.0, 0.0, 1.0), Vector3(1.0, 0.0, 0.0), Vector3(0, 1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftUpperArm", Basis(Vector3(0.0, 0.0, -1.0), Vector3(1.0, 0.0, 0.0), Vector3(0, -1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftLowerArm", Basis(Vector3(0.0, -1.0, 0.0), Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftHand", Basis(Vector3(0.0, 0.0, -1.0), Vector3(1.0, 0.0, 0.0), Vector3(0, -1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightShoulder", Basis(Vector3(0.0, 0.0, -1.0), Vector3(-1.0, 0.0, 0.0), Vector3(0, 1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightUpperArm", Basis(Vector3(0.0, 0.0, 1.0), Vector3(-1.0, 0.0, 0.0), Vector3(0, -1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightLowerArm", Basis(Vector3(0.0, 1.0, 0.0), Vector3(-1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightHand", Basis(Vector3(0.0, 0.0, 1.0), Vector3(-1.0, 0.0, 0.0), Vector3(0, -1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftEye", Basis(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0), Vector3(0, -1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightEye", Basis(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0), Vector3(0, -1.0, 0.0)), Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "jaw", Basis(Vector3(-1.0, 0.0, 0.0), Vector3(0, -0.383, 0.924), Vector3(0, 0.924, 0.383)), Basis(), Vector3(), r_rest_bones); + Basis rightThumbProximal = Basis(Vector3(0.707, -0.5, 0.5), Vector3(-0.707, -0.5, 0.5), Vector3(0, -0.707, -0.707)); + _transform_bone(skeleton, "rightThumbProximal", rightThumbProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightThumbIntermediate", rightThumbProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightThumbDistal", rightThumbProximal, Basis(), Vector3(), r_rest_bones); + Basis rightIndexProximal = Basis(Vector3(0.0, 0.0, 1.0), Vector3(-1.0, 0.0, 0.0), Vector3(0.0, -1.0, 0.0)); + _transform_bone(skeleton, "rightIndexProximal", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightIndexIntermediate", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightIndexDistal", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightMiddleProximal", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightMiddleIntermediate", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightMiddleDistal", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightRingProximal", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightRingIntermediate", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightRingDistal", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightLittleProximal", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightLittleIntermediate", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "rightLittleDistal", rightIndexProximal, Basis(), Vector3(), r_rest_bones); + + Basis leftThumbProximal = Basis(Vector3(0.707, 0.5, -0.5), Vector3(0.707, -0.5, 0.5), Vector3(0, -0.707, -0.707)); + _transform_bone(skeleton, "leftThumbProximal", leftThumbProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftThumbIntermediate", leftThumbProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftThumbDistal", leftThumbProximal, Basis(), Vector3(), r_rest_bones); + Basis leftIndexProximal = Basis(Vector3(0.0, 0.0, -1.0), Vector3(1.0, 0.0, 0.0), Vector3(0.0, -1.0, 0.0)); + _transform_bone(skeleton, "leftIndexProximal", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftIndexIntermediate", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftIndexDistal", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftMiddleProximal", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftMiddleIntermediate", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftMiddleDistal", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftRingProximal", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftRingIntermediate", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftRingDistal", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftLittleProximal", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftLittleIntermediate", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + _transform_bone(skeleton, "leftLittleDistal", leftIndexProximal, Basis(), Vector3(), r_rest_bones); + + // Step 2: Bake the RESET animation from the RestBone to the skeleton. + _fix_skeleton(skeleton, r_rest_bones); + } + if (editor_mesh_3d) { + NodePath path = editor_mesh_3d->get_skeleton_path(); + if (!path.is_empty() && editor_mesh_3d->get_node_or_null(path) && Object::cast_to(editor_mesh_3d->get_node_or_null(path))) { + r_meshes.push_back(editor_mesh_3d); + } + } else if (mesh_3d) { + NodePath path = mesh_3d->get_skeleton_path(); + if (!path.is_empty() && mesh_3d->get_node_or_null(path) && Object::cast_to(mesh_3d->get_node_or_null(path))) { + r_meshes.push_back(mesh_3d); + } + } + int child_count = node->get_child_count(); + for (int i = 0; i < child_count; i++) { + queue.push_back(node->get_child(i)); + } + queue.pop_front(); + } + + queue.push_back(scene); + while (!queue.is_empty()) { + List::Element *E = queue.front(); + Node *node = E->get(); + AnimationPlayer *ap = Object::cast_to(node); + if (ap) { + // Step 3: Key all RESET animation frames to identity. + _align_animations(ap, r_rest_bones); + } + + int child_count = node->get_child_count(); + for (int i = 0; i < child_count; i++) { + queue.push_back(node->get_child(i)); + } + queue.pop_front(); + } +} + +void BakeReset::_align_animations(AnimationPlayer *p_ap, const Map &r_rest_bones) { + ERR_FAIL_NULL(p_ap); + List anim_names; + p_ap->get_animation_list(&anim_names); + for (List::Element *anim_i = anim_names.front(); anim_i; anim_i = anim_i->next()) { + Ref a = p_ap->get_animation(anim_i->get()); + ERR_CONTINUE(a.is_null()); + for (Map::Element *rest_bone_i = r_rest_bones.front(); rest_bone_i; rest_bone_i = rest_bone_i->next()) { + int track = a->find_track(NodePath(rest_bone_i->key()), Animation::TYPE_POSITION_3D); + if (track != -1) { + int new_track = a->add_track(Animation::TYPE_POSITION_3D); + NodePath new_path = NodePath(rest_bone_i->key()); + BakeResetRestBone rest_bone = rest_bone_i->get(); + a->track_set_path(new_track, new_path); + for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { + Vector3 loc; + Error err = a->position_track_get_key(track, key_i, &loc); + ERR_CONTINUE(err); + real_t time = a->track_get_key_time(track, key_i); + loc = loc + rest_bone.loc; + // Apply the reverse of the rest changes to make the key be close to identity transform. + a->position_track_insert_key(new_track, time, loc); + } + a->remove_track(track); + } + track = a->find_track(NodePath(rest_bone_i->key()), Animation::TYPE_ROTATION_3D); + if (track != -1) { + int new_track = a->add_track(Animation::TYPE_ROTATION_3D); + NodePath new_path = NodePath(rest_bone_i->key()); + BakeResetRestBone rest_bone = rest_bone_i->get(); + a->track_set_path(new_track, new_path); + for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { + Quaternion rot; + Error err = a->rotation_track_get_key(track, key_i, &rot); + ERR_CONTINUE(err); + real_t time = a->track_get_key_time(track, key_i); + rot.normalize(); + rot = rot * rest_bone.rest_delta.get_rotation_quaternion(); + rot.normalize(); + // Apply the reverse of the rest changes to make the key be close to identity transform. + a->rotation_track_insert_key(new_track, time, rot); + } + a->remove_track(track); + } + track = a->find_track(NodePath(rest_bone_i->key()), Animation::TYPE_SCALE_3D); + if (track != -1) { + int new_track = a->add_track(Animation::TYPE_SCALE_3D); + NodePath new_path = NodePath(rest_bone_i->key()); + BakeResetRestBone rest_bone = rest_bone_i->get(); + a->track_set_path(new_track, new_path); + for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { + Vector3 scale; + Error err = a->scale_track_get_key(track, key_i, &scale); + ERR_CONTINUE(err); + real_t time = a->track_get_key_time(track, key_i); + scale = Vector3(1, 1, 1) + (rest_bone.rest_delta.get_scale() - scale); + // Apply the reverse of the rest changes to make the key be close to identity transform. + a->scale_track_insert_key(new_track, time, scale); + } + a->remove_track(track); + } + } + } +} + +void BakeReset::_fix_skeleton(Skeleton3D *p_skeleton, Map &r_rest_bones) { + int bone_count = p_skeleton->get_bone_count(); + + // First iterate through all the bones and update the RestBone. + for (int j = 0; j < bone_count; j++) { + StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(j); + BakeResetRestBone &rest_bone = r_rest_bones[final_path]; + rest_bone.rest_local = p_skeleton->get_bone_rest(j).affine_inverse() * p_skeleton->get_bone_rest(j); + } + + for (int i = 0; i < bone_count; i++) { + int parent_bone = p_skeleton->get_bone_parent(i); + String path = p_skeleton->get_owner()->get_path_to(p_skeleton); + StringName final_path = String(path) + String(":") + p_skeleton->get_bone_name(parent_bone); + if (parent_bone >= 0) { + r_rest_bones[final_path].children.push_back(i); + } + } + + //When we rotate a bone, we also have to move all of its children in the opposite direction + for (int i = 0; i < bone_count; i++) { + StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(i); + r_rest_bones[final_path].rest_local = r_rest_bones[final_path].rest_local * Transform3D(r_rest_bones[final_path].rest_delta, r_rest_bones[final_path].loc); + + //Iterate through the children and rotate them in the opposite direction. + for (int j = 0; j < r_rest_bones[final_path].children.size(); j++) { + int child_index = r_rest_bones[final_path].children[j]; + StringName child_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(child_index); + r_rest_bones[child_path].rest_local = Transform3D(r_rest_bones[final_path].rest_delta, r_rest_bones[final_path].loc).affine_inverse() * r_rest_bones[child_path].rest_local; + } + } + + for (int i = 0; i < bone_count; i++) { + StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(i); + ERR_CONTINUE(!r_rest_bones.has(final_path)); + Transform3D rest_transform = r_rest_bones[final_path].rest_local; + p_skeleton->set_bone_rest(i, p_skeleton->get_bone_rest(i) * rest_transform); + } +} + +void BakeReset::_transform_bone(Skeleton3D *p_skeleton, String bone_name, Basis vrm_rest_rot, Basis rot, Vector3 loc, Map &r_rest_bones) { + BakeResetRestBone rest_bone; + String new_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + ":" + bone_name; + rest_bone.rest_delta = vrm_rest_rot; + rest_bone.loc = loc; + r_rest_bones[new_path] = rest_bone; +} diff --git a/editor/import/editor_importer_bake_reset.h b/editor/import/editor_importer_bake_reset.h new file mode 100644 index 000000000000..de609dc675bf --- /dev/null +++ b/editor/import/editor_importer_bake_reset.h @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* editor_importer_bake_reset.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 RESOURCE_IMPORTER_BAKE_RESET_H +#define RESOURCE_IMPORTER_BAKE_RESET_H + +#include "core/math/transform_3d.h" +#include "scene/main/node.h" + +class Skeleton3D; +class AnimationPlayer; +class BakeReset { + struct BakeResetRestBone { + Transform3D rest_local; + Basis rest_delta; + Vector3 loc; + Vector children; + }; + +public: + void _bake_animation_pose(Node *scene, const String &p_bake_anim); + +private: + void _fix_skeleton(Skeleton3D *p_skeleton, Map &r_rest_bones); + void _align_animations(AnimationPlayer *p_ap, const Map &r_rest_bones); + void _transform_bone(Skeleton3D *p_skeleton, String bone_name, Basis vrm_rest_rot, Basis rot, Vector3 loc, Map &r_rest_bones); +}; +#endif diff --git a/editor/import/editor_importer_point_parent_to_child.cpp b/editor/import/editor_importer_point_parent_to_child.cpp new file mode 100644 index 000000000000..e7404a6cc675 --- /dev/null +++ b/editor/import/editor_importer_point_parent_to_child.cpp @@ -0,0 +1,301 @@ +/*************************************************************************/ +/* editor_importer_point_parent_to_child.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "editor/import/editor_importer_point_parent_to_child.h" +#include "core/math/transform_3d.h" +#include "resource_importer_scene.h" +#include "scene/3d/importer_mesh_instance_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/animation/animation_player.h" + +void PointParentToChild::_parent_to_child(Node *scene) { + Map r_rest_bones; + Vector r_meshes; + List queue; + queue.push_back(scene); + while (!queue.is_empty()) { + List::Element *E = queue.front(); + Node *node = E->get(); + if (Object::cast_to(node)) { + Skeleton3D *skeleton = Object::cast_to(node); + _fix_skeleton(skeleton, r_rest_bones); + } + ImporterMeshInstance3D *editor_mesh_3d = Object::cast_to(node); + MeshInstance3D *mesh_3d = Object::cast_to(node); + if (editor_mesh_3d) { + NodePath path = editor_mesh_3d->get_skeleton_path(); + if (!path.is_empty() && editor_mesh_3d->get_node_or_null(path) && Object::cast_to(editor_mesh_3d->get_node_or_null(path))) { + r_meshes.push_back(editor_mesh_3d); + } + } else if (mesh_3d) { + NodePath path = mesh_3d->get_skeleton_path(); + if (!path.is_empty() && mesh_3d->get_node_or_null(path) && Object::cast_to(mesh_3d->get_node_or_null(path))) { + r_meshes.push_back(mesh_3d); + } + } + int child_count = node->get_child_count(); + for (int i = 0; i < child_count; i++) { + queue.push_back(node->get_child(i)); + } + queue.pop_front(); + } + _fix_meshes(r_rest_bones, r_meshes); + + queue.clear(); + queue.push_back(scene); + while (!queue.is_empty()) { + List::Element *E = queue.front(); + Node *node = E->get(); + AnimationPlayer *ap = Object::cast_to(node); + _align_animations(ap, r_rest_bones); + + int child_count = node->get_child_count(); + for (int i = 0; i < child_count; i++) { + queue.push_back(node->get_child(i)); + } + queue.pop_front(); + } +} + +Vector3 PointParentToChild::_get_perpendicular_vector(Vector3 v) { + Vector3 perpendicular; + if (v[0] != 0 && v[1] != 0) { + perpendicular = Vector3(0, 0, 1).cross(v).normalized(); + } else { + perpendicular = Vector3(1, 0, 0); + } + return perpendicular; +} + +Quaternion PointParentToChild::_align_vectors(Vector3 a, Vector3 b) { + a.normalize(); + b.normalize(); + if (a.length_squared() != 0 && b.length_squared() != 0) { + //Find the axis perpendicular to both vectors and rotate along it by the angular difference + Vector3 perpendicular = a.cross(b).normalized(); + float angleDiff = a.angle_to(b); + if (perpendicular.length_squared() == 0) { + perpendicular = _get_perpendicular_vector(a); + } + return Quaternion(perpendicular, angleDiff); + } else { + return Quaternion(); + } +} + +void PointParentToChild::_fix_skeleton(Skeleton3D *p_skeleton, Map &r_rest_bones) { + int bone_count = p_skeleton->get_bone_count(); + + //First iterate through all the bones and create a RestBone for it with an empty centroid + for (int j = 0; j < bone_count; j++) { + RestBone rest_bone; + + String path = p_skeleton->get_name(); + Node *current_node = p_skeleton->get_parent(); + while (current_node && current_node != p_skeleton->get_owner()) { + path = String(current_node->get_name()) + "/" + path; + current_node = current_node->get_parent(); + } + rest_bone.parent_index = p_skeleton->get_bone_parent(j); + rest_bone.rest_local_before = p_skeleton->get_bone_rest(j).affine_inverse() * p_skeleton->get_bone_rest(j); + rest_bone.rest_local_after = rest_bone.rest_local_before; + r_rest_bones.insert(String(path) + String(":") + p_skeleton->get_bone_name(j), rest_bone); + } + + //We iterate through again, and add the child's position to the centroid of its parent. + //These position are local to the parent which means (0, 0, 0) is right where the parent is. + for (int i = 0; i < bone_count; i++) { + int parent_bone = p_skeleton->get_bone_parent(i); + String path = p_skeleton->get_owner()->get_path_to(p_skeleton); + if (parent_bone < 0) { + continue; + } + StringName parent_bone_path = String(path) + String(":") + p_skeleton->get_bone_name(parent_bone); + r_rest_bones[parent_bone_path].children_centroid_direction = r_rest_bones[parent_bone_path].children_centroid_direction + (p_skeleton->get_bone_rest(i).affine_inverse() * p_skeleton->get_bone_rest(i)).origin; + r_rest_bones[parent_bone_path].children.push_back(i); + } + + //Point leaf bones to parent + for (int bone_i = 0; bone_i < bone_count; bone_i++) { + String path = p_skeleton->get_owner()->get_path_to(p_skeleton); + if (bone_i < 0) { + continue; + } + StringName bone_path = String(path) + String(":") + p_skeleton->get_bone_name(bone_i); + PointParentToChild::RestBone &leaf_bone = r_rest_bones[bone_path]; + if (!leaf_bone.children.size()) { + StringName parent_bone_path = String(path) + String(":") + p_skeleton->get_bone_name(leaf_bone.parent_index); + leaf_bone.children_centroid_direction = r_rest_bones[parent_bone_path].children_centroid_direction; + } + } + + //We iterate again to point each bone to the centroid + //When we rotate a bone, we also have to move all of its children in the opposite direction + for (int bone_i = 0; bone_i < bone_count; bone_i++) { + String path = p_skeleton->get_owner()->get_path_to(p_skeleton); + if (bone_i < 0) { + continue; + } + StringName bone_path = String(path) + String(":") + p_skeleton->get_bone_name(bone_i); + r_rest_bones[bone_path].rest_delta = _align_vectors(Vector3(0, 1, 0), r_rest_bones[bone_path].children_centroid_direction); + r_rest_bones[bone_path].rest_local_after.basis = r_rest_bones[bone_path].rest_local_after.basis * r_rest_bones[bone_path].rest_delta; + + //Iterate through the children and rotate them in the opposite direction. + for (int j = 0; j < r_rest_bones[bone_path].children.size(); j++) { + int child_index = r_rest_bones[bone_path].children[j]; + String path = p_skeleton->get_owner()->get_path_to(p_skeleton); + if (child_index < 0) { + continue; + } + StringName child_bone_path = String(path) + String(":") + p_skeleton->get_bone_name(child_index); + r_rest_bones[child_bone_path].rest_local_after = Transform3D(r_rest_bones[bone_path].rest_delta.inverse(), Vector3()) * r_rest_bones[child_bone_path].rest_local_after; + } + } + + //One last iteration to apply the transforms we calculated + for (int bone_i = 0; bone_i < bone_count; bone_i++) { + String path = p_skeleton->get_owner()->get_path_to(p_skeleton); + if (bone_i < 0) { + continue; + } + StringName bone_path = String(path) + String(":") + p_skeleton->get_bone_name(bone_i); + p_skeleton->set_bone_rest(bone_i, p_skeleton->get_bone_rest(bone_i) * r_rest_bones[bone_path].rest_local_after); + } +} + +void PointParentToChild::_fix_meshes(Map &r_rest_bones, Vector p_meshes) { + for (int32_t mesh_i = 0; mesh_i < p_meshes.size(); mesh_i++) { + Node3D *mi = p_meshes.write[mesh_i]; + ImporterMeshInstance3D *importer_node_3d = Object::cast_to(mi); + MeshInstance3D *mesh_3d = Object::cast_to(mi); + + Skeleton3D *skeleton = nullptr; + if (importer_node_3d) { + NodePath skeleton_path = importer_node_3d->get_skeleton_path(); + Node *node = importer_node_3d->get_node_or_null(skeleton_path); + skeleton = Object::cast_to(node); + } else if (mesh_3d) { + NodePath skeleton_path = mesh_3d->get_skeleton_path(); + Node *node = mesh_3d->get_node_or_null(skeleton_path); + skeleton = Object::cast_to(node); + } + ERR_CONTINUE(!skeleton); + Ref skin; + if (importer_node_3d) { + skin = importer_node_3d->get_skin(); + } else if (mesh_3d) { + skin = mesh_3d->get_skin(); + } + ERR_CONTINUE(!importer_node_3d && !mesh_3d); + if (importer_node_3d && skin.is_null()) { + skin = skeleton->register_skin(skin)->get_skin(); + importer_node_3d->set_skin(skin); + } else if (mesh_3d && skin.is_null()) { + skin = skeleton->register_skin(skin)->get_skin(); + mesh_3d->set_skin(skin); + } else { + skin = skin->duplicate(); + if (importer_node_3d) { + importer_node_3d->set_skin(skin); + } else if (mesh_3d) { + mesh_3d->set_skin(skin); + } + } + for (int32_t bind_i = 0; bind_i < skin->get_bind_count(); bind_i++) { + String bind_name = skin->get_bind_name(bind_i); + int32_t bone_index = -1; + if (bind_name.is_empty()) { + bone_index = skin->get_bind_bone(bind_i); + } else { + bone_index = skeleton->find_bone(bind_name); + } + if (bone_index == -1) { + continue; + } + String path = skeleton->get_owner()->get_path_to(skeleton); + if (bone_index < 0) { + continue; + } + StringName bone_path = String(path) + String(":") + skeleton->get_bone_name(bone_index); + RestBone rest_bone = r_rest_bones[bone_path]; + Transform3D pose = skin->get_bind_pose(bind_i); + skin->set_bind_pose(bind_i, Transform3D(rest_bone.rest_delta.inverse()) * pose); + } + } +} + +void PointParentToChild::_align_animations(AnimationPlayer *p_ap, const Map &p_rest_bones) { + ERR_FAIL_NULL(p_ap); + List anim_names; + p_ap->get_animation_list(&anim_names); + for (List::Element *anim_i = anim_names.front(); anim_i; anim_i = anim_i->next()) { + Ref a = p_ap->get_animation(anim_i->get()); + ERR_CONTINUE(a.is_null()); + for (Map::Element *rest_bone_i = p_rest_bones.front(); rest_bone_i; rest_bone_i = rest_bone_i->next()) { + int track = a->find_track(NodePath(rest_bone_i->key()), Animation::TrackType::TYPE_ROTATION_3D); + if (track != -1) { + int new_track = a->add_track(Animation::TYPE_ROTATION_3D); + NodePath new_path = NodePath(rest_bone_i->key()); + RestBone rest_bone = rest_bone_i->get(); + a->track_set_path(new_track, new_path); + for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { + Quaternion rot; + Error err = a->rotation_track_get_key(track, key_i, &rot); + ERR_CONTINUE(err); + real_t time = a->track_get_key_time(track, key_i); + rot.normalize(); + rot = rot * rest_bone.rest_delta.get_rotation_quaternion(); + rot.normalize(); + // Apply the reverse of the rest changes to make the key be close to identity transform. + a->rotation_track_insert_key(new_track, time, rot); + } + a->remove_track(track); + } + track = a->find_track(NodePath(rest_bone_i->key()), Animation::TrackType::TYPE_SCALE_3D); + if (a->track_get_type(track) == Animation::TYPE_SCALE_3D) { + int new_track = a->add_track(Animation::TYPE_SCALE_3D); + NodePath new_path = NodePath(rest_bone_i->key()); + RestBone rest_bone = rest_bone_i->get(); + a->track_set_path(new_track, new_path); + for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { + Vector3 scale; + Error err = a->scale_track_get_key(track, key_i, &scale); + ERR_CONTINUE(err); + real_t time = a->track_get_key_time(track, key_i); + scale = Vector3(1, 1, 1) + (rest_bone.rest_delta.get_scale() - scale); + // Apply the reverse of the rest changes to make the key be close to identity transform. + a->scale_track_insert_key(new_track, time, scale); + } + a->remove_track(track); + } + } + } +} diff --git a/editor/import/editor_importer_point_parent_to_child.h b/editor/import/editor_importer_point_parent_to_child.h new file mode 100644 index 000000000000..91c101629781 --- /dev/null +++ b/editor/import/editor_importer_point_parent_to_child.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* editor_importer_point_parent_to_child.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 RESOURCE_IMPORTER_POINT_PARENT_TO_CHILD_H +#define RESOURCE_IMPORTER_POINT_PARENT_TO_CHILD_H +#include "core/math/transform_3d.h" +#include "resource_importer_scene.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/animation/animation_player.h" + +class PointParentToChild { + struct RestBone { + Transform3D rest_local_before; + Transform3D rest_local_after; + Basis rest_delta; + Vector3 children_centroid_direction; + int parent_index; + Vector children; + }; + +public: + void _parent_to_child(Node *scene); + +private: + void _fix_meshes(Map &r_rest_bones, Vector p_meshes); + void _fix_skeleton(Skeleton3D *p_skeleton, Map &r_rest_bones); + void _align_animations(AnimationPlayer *p_ap, const Map &p_rest_bones); + Vector3 _get_perpendicular_vector(Vector3 v); + Quaternion _align_vectors(Vector3 a, Vector3 b); +}; + +#endif diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index bebf05d48107..8800386c9650 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -30,9 +30,11 @@ #include "resource_importer_scene.h" +#include "core/error/error_macros.h" #include "core/io/resource_saver.h" #include "editor/editor_node.h" - +#include "editor/import/editor_importer_bake_reset.h" +#include "editor/import/editor_importer_point_parent_to_child.h" #include "editor/import/scene_import_settings.h" #include "scene/3d/area_3d.h" #include "scene/3d/collision_shape_3d.h" @@ -1426,6 +1428,8 @@ void ResourceImporterScene::get_import_options(List *r_options, in r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.1)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skins/use_named_skins"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import"), true)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/point_parent_bone_to_children"), true)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/bake_reset_animation"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 15)); r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "import_script/path", PROPERTY_HINT_FILE, script_ext_hint), "")); @@ -1951,6 +1955,18 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p _post_fix_node(scene, scene, collision_map, scanned_meshes, node_data, material_data, animation_data, fps); + bool use_point_parent_bone_to_children = p_options["animation/point_parent_bone_to_children"]; + if (use_point_parent_bone_to_children) { + PointParentToChild parent_to_child; + parent_to_child._parent_to_child(scene); + } + + bool use_bake_reset_animation = p_options["animation/bake_reset_animation"]; + if (use_bake_reset_animation) { + BakeReset bake_reset; + bake_reset._bake_animation_pose(scene, "RESET"); + } + String root_type = p_options["nodes/root_type"]; root_type = root_type.split(" ")[0]; // full root_type is "ClassName (filename.gd)" for a script global class.