summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/Skeleton3D.xml4
-rw-r--r--doc/classes/SkeletonProfile.xml8
-rw-r--r--doc/classes/SkeletonProfileHumanoid.xml2
-rw-r--r--editor/import/post_import_plugin_skeleton_rest_fixer.cpp95
-rw-r--r--editor/import/post_import_plugin_skeleton_track_organizer.cpp116
-rw-r--r--editor/import/post_import_plugin_skeleton_track_organizer.h46
-rw-r--r--editor/import/resource_importer_scene.cpp243
-rw-r--r--editor/import/resource_importer_scene.h2
-rw-r--r--editor/plugins/bone_map_editor_plugin.cpp17
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp4
-rw-r--r--scene/3d/skeleton_3d.cpp22
-rw-r--r--scene/3d/skeleton_3d.h4
-rw-r--r--scene/animation/animation_player.cpp26
-rw-r--r--scene/animation/animation_player.h2
-rw-r--r--scene/animation/animation_tree.cpp42
-rw-r--r--scene/animation/animation_tree.h2
-rw-r--r--scene/resources/skeleton_profile.cpp44
-rw-r--r--scene/resources/skeleton_profile.h9
18 files changed, 580 insertions, 108 deletions
diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml
index f271220989..6295724aa2 100644
--- a/doc/classes/Skeleton3D.xml
+++ b/doc/classes/Skeleton3D.xml
@@ -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>
diff --git a/doc/classes/SkeletonProfile.xml b/doc/classes/SkeletonProfile.xml
index a7f5f7a0a6..ca82993927 100644
--- a/doc/classes/SkeletonProfile.xml
+++ b/doc/classes/SkeletonProfile.xml
@@ -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">
diff --git a/doc/classes/SkeletonProfileHumanoid.xml b/doc/classes/SkeletonProfileHumanoid.xml
index 065184244e..11f0521718 100644
--- a/doc/classes/SkeletonProfileHumanoid.xml
+++ b/doc/classes/SkeletonProfileHumanoid.xml
@@ -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>
diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
index 01e145e766..4f00bd120a 100644
--- a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
@@ -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));
@@ -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");
@@ -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());
@@ -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);
+ }
}
}
}
diff --git a/editor/import/post_import_plugin_skeleton_track_organizer.cpp b/editor/import/post_import_plugin_skeleton_track_organizer.cpp
new file mode 100644
index 0000000000..25bb7f793e
--- /dev/null
+++ b/editor/import/post_import_plugin_skeleton_track_organizer.cpp
@@ -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() {
+}
diff --git a/editor/import/post_import_plugin_skeleton_track_organizer.h b/editor/import/post_import_plugin_skeleton_track_organizer.h
new file mode 100644
index 0000000000..1830861430
--- /dev/null
+++ b/editor/import/post_import_plugin_skeleton_track_organizer.h
@@ -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
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index 860269bfcb..fab3e000cf 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -743,6 +743,163 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
return p_node;
}
+Node *ResourceImporterScene::_pre_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps) {
+ // children first
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ Node *r = _pre_fix_animations(p_node->get_child(i), p_root, p_node_data, p_animation_data, p_animation_fps);
+ if (!r) {
+ i--; //was erased
+ }
+ }
+
+ String import_id = p_node->get_meta("import_id", "PATH:" + p_root->get_path_to(p_node));
+
+ Dictionary node_settings;
+ if (p_node_data.has(import_id)) {
+ node_settings = p_node_data[import_id];
+ }
+
+ {
+ //make sure this is unique
+ node_settings = node_settings.duplicate(true);
+ //fill node settings for this node with default values
+ List<ImportOption> iopts;
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, &iopts);
+ for (const ImportOption &E : iopts) {
+ if (!node_settings.has(E.option.name)) {
+ node_settings[E.option.name] = E.default_value;
+ }
+ }
+ }
+
+ if (Object::cast_to<AnimationPlayer>(p_node)) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node);
+
+ Array animation_clips;
+ {
+ int clip_count = node_settings["clips/amount"];
+
+ for (int i = 0; i < clip_count; i++) {
+ String name = node_settings["clip_" + itos(i + 1) + "/name"];
+ int from_frame = node_settings["clip_" + itos(i + 1) + "/start_frame"];
+ int end_frame = node_settings["clip_" + itos(i + 1) + "/end_frame"];
+ Animation::LoopMode loop_mode = static_cast<Animation::LoopMode>((int)node_settings["clip_" + itos(i + 1) + "/loop_mode"]);
+ bool save_to_file = node_settings["clip_" + itos(i + 1) + "/save_to_file/enabled"];
+ bool save_to_path = node_settings["clip_" + itos(i + 1) + "/save_to_file/path"];
+ bool save_to_file_keep_custom = node_settings["clip_" + itos(i + 1) + "/save_to_file/keep_custom_tracks"];
+
+ animation_clips.push_back(name);
+ animation_clips.push_back(from_frame / p_animation_fps);
+ animation_clips.push_back(end_frame / p_animation_fps);
+ animation_clips.push_back(loop_mode);
+ animation_clips.push_back(save_to_file);
+ animation_clips.push_back(save_to_path);
+ animation_clips.push_back(save_to_file_keep_custom);
+ }
+ }
+
+ if (animation_clips.size()) {
+ _create_clips(ap, animation_clips, true);
+ } else {
+ List<StringName> anims;
+ ap->get_animation_list(&anims);
+ AnimationImportTracks import_tracks_mode[TRACK_CHANNEL_MAX] = {
+ AnimationImportTracks(int(node_settings["import_tracks/position"])),
+ AnimationImportTracks(int(node_settings["import_tracks/rotation"])),
+ AnimationImportTracks(int(node_settings["import_tracks/scale"]))
+ };
+ if (anims.size() > 1 && (import_tracks_mode[0] != ANIMATION_IMPORT_TRACKS_IF_PRESENT || import_tracks_mode[1] != ANIMATION_IMPORT_TRACKS_IF_PRESENT || import_tracks_mode[2] != ANIMATION_IMPORT_TRACKS_IF_PRESENT)) {
+ _optimize_track_usage(ap, import_tracks_mode);
+ }
+ }
+ }
+
+ return p_node;
+}
+
+Node *ResourceImporterScene::_post_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps) {
+ // children first
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ Node *r = _post_fix_animations(p_node->get_child(i), p_root, p_node_data, p_animation_data, p_animation_fps);
+ if (!r) {
+ i--; //was erased
+ }
+ }
+
+ String import_id = p_node->get_meta("import_id", "PATH:" + p_root->get_path_to(p_node));
+
+ Dictionary node_settings;
+ if (p_node_data.has(import_id)) {
+ node_settings = p_node_data[import_id];
+ }
+
+ {
+ //make sure this is unique
+ node_settings = node_settings.duplicate(true);
+ //fill node settings for this node with default values
+ List<ImportOption> iopts;
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, &iopts);
+ for (const ImportOption &E : iopts) {
+ if (!node_settings.has(E.option.name)) {
+ node_settings[E.option.name] = E.default_value;
+ }
+ }
+ }
+
+ if (Object::cast_to<AnimationPlayer>(p_node)) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node);
+
+ bool use_optimizer = node_settings["optimizer/enabled"];
+ float anim_optimizer_linerr = node_settings["optimizer/max_linear_error"];
+ float anim_optimizer_angerr = node_settings["optimizer/max_angular_error"];
+ float anim_optimizer_maxang = node_settings["optimizer/max_angle"];
+
+ if (use_optimizer) {
+ _optimize_animations(ap, anim_optimizer_linerr, anim_optimizer_angerr, anim_optimizer_maxang);
+ }
+
+ bool use_compression = node_settings["compression/enabled"];
+ int anim_compression_page_size = node_settings["compression/page_size"];
+
+ if (use_compression) {
+ _compress_animations(ap, anim_compression_page_size);
+ }
+
+ List<StringName> anims;
+ ap->get_animation_list(&anims);
+ for (const StringName &name : anims) {
+ Ref<Animation> anim = ap->get_animation(name);
+ if (p_animation_data.has(name)) {
+ Dictionary anim_settings = p_animation_data[name];
+ {
+ //fill with default values
+ List<ImportOption> iopts;
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION, &iopts);
+ for (const ImportOption &F : iopts) {
+ if (!anim_settings.has(F.option.name)) {
+ anim_settings[F.option.name] = F.default_value;
+ }
+ }
+ }
+
+ anim->set_loop_mode(static_cast<Animation::LoopMode>((int)anim_settings["settings/loop_mode"]));
+ bool save = anim_settings["save_to_file/enabled"];
+ String path = anim_settings["save_to_file/path"];
+ bool keep_custom = anim_settings["save_to_file/keep_custom_tracks"];
+
+ Ref<Animation> saved_anim = _save_animation_to_file(anim, save, path, keep_custom);
+
+ if (saved_anim != anim) {
+ Ref<AnimationLibrary> al = ap->get_animation_library(ap->find_animation_library(anim));
+ al->add_animation(name, saved_anim); //replace
+ }
+ }
+ }
+ }
+
+ return p_node;
+}
+
Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Pair<PackedVector3Array, PackedInt32Array> &r_occluder_arrays, HashSet<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps) {
// children first
for (int i = 0; i < p_node->get_child_count(); i++) {
@@ -1012,83 +1169,6 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, p_root, p_node, Ref<Resource>(), node_settings);
}
- bool use_optimizer = node_settings["optimizer/enabled"];
- float anim_optimizer_linerr = node_settings["optimizer/max_linear_error"];
- float anim_optimizer_angerr = node_settings["optimizer/max_angular_error"];
- float anim_optimizer_maxang = node_settings["optimizer/max_angle"];
-
- if (use_optimizer) {
- _optimize_animations(ap, anim_optimizer_linerr, anim_optimizer_angerr, anim_optimizer_maxang);
- }
-
- Array animation_clips;
- {
- int clip_count = node_settings["clips/amount"];
-
- for (int i = 0; i < clip_count; i++) {
- String name = node_settings["clip_" + itos(i + 1) + "/name"];
- int from_frame = node_settings["clip_" + itos(i + 1) + "/start_frame"];
- int end_frame = node_settings["clip_" + itos(i + 1) + "/end_frame"];
- Animation::LoopMode loop_mode = static_cast<Animation::LoopMode>((int)node_settings["clip_" + itos(i + 1) + "/loop_mode"]);
- bool save_to_file = node_settings["clip_" + itos(i + 1) + "/save_to_file/enabled"];
- bool save_to_path = node_settings["clip_" + itos(i + 1) + "/save_to_file/path"];
- bool save_to_file_keep_custom = node_settings["clip_" + itos(i + 1) + "/save_to_file/keep_custom_tracks"];
-
- animation_clips.push_back(name);
- animation_clips.push_back(from_frame / p_animation_fps);
- animation_clips.push_back(end_frame / p_animation_fps);
- animation_clips.push_back(loop_mode);
- animation_clips.push_back(save_to_file);
- animation_clips.push_back(save_to_path);
- animation_clips.push_back(save_to_file_keep_custom);
- }
- }
-
- if (animation_clips.size()) {
- _create_clips(ap, animation_clips, true);
- } else {
- List<StringName> anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
- Ref<Animation> anim = ap->get_animation(name);
- if (p_animation_data.has(name)) {
- Dictionary anim_settings = p_animation_data[name];
- {
- //fill with default values
- List<ImportOption> iopts;
- get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION, &iopts);
- for (const ImportOption &F : iopts) {
- if (!anim_settings.has(F.option.name)) {
- anim_settings[F.option.name] = F.default_value;
- }
- }
- }
-
- anim->set_loop_mode(static_cast<Animation::LoopMode>((int)anim_settings["settings/loop_mode"]));
- bool save = anim_settings["save_to_file/enabled"];
- String path = anim_settings["save_to_file/path"];
- bool keep_custom = anim_settings["save_to_file/keep_custom_tracks"];
-
- Ref<Animation> saved_anim = _save_animation_to_file(anim, save, path, keep_custom);
-
- if (saved_anim != anim) {
- Ref<AnimationLibrary> al = ap->get_animation_library(ap->find_animation_library(anim));
- al->add_animation(name, saved_anim); //replace
- }
- }
- }
-
- AnimationImportTracks import_tracks_mode[TRACK_CHANNEL_MAX] = {
- AnimationImportTracks(int(node_settings["import_tracks/position"])),
- AnimationImportTracks(int(node_settings["import_tracks/rotation"])),
- AnimationImportTracks(int(node_settings["import_tracks/scale"]))
- };
-
- if (anims.size() > 1 && (import_tracks_mode[0] != ANIMATION_IMPORT_TRACKS_IF_PRESENT || import_tracks_mode[1] != ANIMATION_IMPORT_TRACKS_IF_PRESENT || import_tracks_mode[2] != ANIMATION_IMPORT_TRACKS_IF_PRESENT)) {
- _optimize_track_usage(ap, import_tracks_mode);
- }
- }
-
if (post_importer_plugins.size()) {
List<StringName> anims;
ap->get_animation_list(&anims);
@@ -1113,13 +1193,6 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
}
}
}
-
- bool use_compression = node_settings["compression/enabled"];
- int anim_compression_page_size = node_settings["compression/page_size"];
-
- if (use_compression) {
- _compress_animations(ap, anim_compression_page_size);
- }
}
return p_node;
@@ -2099,7 +2172,9 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
post_importer_plugins.write[i]->pre_process(scene, p_options);
}
+ _pre_fix_animations(scene, scene, node_data, animation_data, fps);
_post_fix_node(scene, scene, collision_map, occluder_arrays, scanned_meshes, node_data, material_data, animation_data, fps);
+ _post_fix_animations(scene, scene, node_data, animation_data, fps);
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.
diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h
index db6ca0cdcf..b336931476 100644
--- a/editor/import/resource_importer_scene.h
+++ b/editor/import/resource_importer_scene.h
@@ -274,7 +274,9 @@ public:
virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; }
Node *_pre_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &r_collision_map, Pair<PackedVector3Array, PackedInt32Array> *r_occluder_arrays, List<Pair<NodePath, Node *>> &r_node_renames);
+ Node *_pre_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps);
Node *_post_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Pair<PackedVector3Array, PackedInt32Array> &r_occluder_arrays, HashSet<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps);
+ Node *_post_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps);
Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks);
void _create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all);
diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp
index 967a95be9d..9a00bb93b8 100644
--- a/editor/plugins/bone_map_editor_plugin.cpp
+++ b/editor/plugins/bone_map_editor_plugin.cpp
@@ -33,6 +33,7 @@
#include "editor/editor_scale.h"
#include "editor/import/post_import_plugin_skeleton_renamer.h"
#include "editor/import/post_import_plugin_skeleton_rest_fixer.h"
+#include "editor/import/post_import_plugin_skeleton_track_organizer.h"
#include "editor/import/scene_import_settings.h"
void BoneMapperButton::fetch_textures() {
@@ -379,11 +380,15 @@ void BoneMapEditor::create_editors() {
}
void BoneMapEditor::fetch_objects() {
+ skeleton = nullptr;
// Hackey... but it may be the easist way to get a selected object from "ImporterScene".
SceneImportSettings *si = SceneImportSettings::get_singleton();
if (!si) {
return;
}
+ if (!si->is_visible()) {
+ return;
+ }
Node *selected = si->get_selected_node();
if (selected) {
Skeleton3D *sk = Object::cast_to<Skeleton3D>(selected);
@@ -404,11 +409,11 @@ void BoneMapEditor::_notification(int p_what) {
create_editors();
} break;
case NOTIFICATION_EXIT_TREE: {
- if (!bone_mapper) {
- return;
+ if (bone_mapper) {
+ remove_child(bone_mapper);
+ bone_mapper->queue_delete();
}
- remove_child(bone_mapper);
- bone_mapper->queue_delete();
+ skeleton = nullptr;
} break;
}
}
@@ -444,6 +449,10 @@ BoneMapEditorPlugin::BoneMapEditorPlugin() {
inspector_plugin.instantiate();
add_inspector_plugin(inspector_plugin);
+ Ref<PostImportPluginSkeletonTrackOrganizer> post_import_plugin_track_organizer;
+ post_import_plugin_track_organizer.instantiate();
+ add_scene_post_import_plugin(post_import_plugin_track_organizer);
+
Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer;
post_import_plugin_renamer.instantiate();
add_scene_post_import_plugin(post_import_plugin_renamer);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 93e44c8ca0..5d6c56d5f0 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -157,7 +157,7 @@ void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance)
if (split.size() == 3 && split[0] == "bones") {
int bone_idx = split[1].to_int();
if (split[2] == "position") {
- te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, skeleton->get(p_path));
+ te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, (Vector3)skeleton->get(p_path) / skeleton->get_motion_scale());
}
if (split[2] == "rotation") {
te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_ROTATION_3D, skeleton->get(p_path));
@@ -319,7 +319,7 @@ void Skeleton3DEditor::insert_keys(const bool p_all_bones) {
}
if (pos_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_POSITION_3D))) {
- te->insert_transform_key(skeleton, name, Animation::TYPE_POSITION_3D, skeleton->get_bone_pose_position(i));
+ te->insert_transform_key(skeleton, name, Animation::TYPE_POSITION_3D, skeleton->get_bone_pose_position(i) / skeleton->get_motion_scale());
}
if (rot_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_ROTATION_3D))) {
te->insert_transform_key(skeleton, name, Animation::TYPE_ROTATION_3D, skeleton->get_bone_pose_rotation(i));
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index b342660b85..4c38fccc8b 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -493,6 +493,19 @@ int Skeleton3D::get_bone_axis_forward_enum(int p_bone) {
return bones[p_bone].rest_bone_forward_axis;
}
+void Skeleton3D::set_motion_scale(float p_motion_scale) {
+ if (p_motion_scale <= 0) {
+ motion_scale = 1;
+ ERR_FAIL_MSG("Motion scale must be larger than 0.");
+ }
+ motion_scale = p_motion_scale;
+}
+
+float Skeleton3D::get_motion_scale() const {
+ ERR_FAIL_COND_V(motion_scale <= 0, 1);
+ return motion_scale;
+}
+
// Skeleton creation api
void Skeleton3D::add_bone(const String &p_name) {
@@ -1255,6 +1268,9 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("force_update_all_bone_transforms"), &Skeleton3D::force_update_all_bone_transforms);
ClassDB::bind_method(D_METHOD("force_update_bone_child_transform", "bone_idx"), &Skeleton3D::force_update_bone_children_transforms);
+ ClassDB::bind_method(D_METHOD("set_motion_scale", "motion_scale"), &Skeleton3D::set_motion_scale);
+ ClassDB::bind_method(D_METHOD("get_motion_scale"), &Skeleton3D::get_motion_scale);
+
// Helper functions
ClassDB::bind_method(D_METHOD("global_pose_to_world_transform", "global_pose"), &Skeleton3D::global_pose_to_world_transform);
ClassDB::bind_method(D_METHOD("world_transform_to_global_pose", "world_transform"), &Skeleton3D::world_transform_to_global_pose);
@@ -1278,15 +1294,13 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton3D::get_modification_stack);
ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton3D::execute_modifications);
-#ifndef _3D_DISABLED
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_scale", PROPERTY_HINT_RANGE, "0.001,10,0.001,or_greater"), "set_motion_scale", "get_motion_scale");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_rest_only"), "set_show_rest_only", "is_show_rest_only");
+#ifndef _3D_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "animate_physical_bones"), "set_animate_physical_bones", "get_animate_physical_bones");
#endif // _3D_DISABLED
-#ifdef TOOLS_ENABLED
ADD_SIGNAL(MethodInfo("pose_updated"));
-#endif // TOOLS_ENABLED
-
ADD_SIGNAL(MethodInfo("bone_pose_changed", PropertyInfo(Variant::INT, "bone_idx")));
ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx")));
ADD_SIGNAL(MethodInfo("show_rest_only_changed"));
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index fcb8774c3d..8b69410a39 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -146,6 +146,7 @@ private:
bool rest_dirty = false;
bool show_rest_only = false;
+ float motion_scale = 1.0;
uint64_t version = 1;
@@ -211,6 +212,9 @@ public:
bool is_show_rest_only() const;
void clear_bones();
+ void set_motion_scale(float p_motion_scale);
+ float get_motion_scale() const;
+
// posing api
void set_bone_pose_position(int p_bone, const Vector3 &p_position);
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index df6ef16b4f..2007dc6405 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -454,6 +454,23 @@ static void _call_object(Object *p_object, const StringName &p_method, const Vec
}
}
+Variant AnimationPlayer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) {
+ switch (p_anim->track_get_type(p_track)) {
+#ifndef _3D_DISABLED
+ case Animation::TYPE_POSITION_3D: {
+ if (p_object_idx >= 0) {
+ const Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_object);
+ return Vector3(p_value) * skel->get_motion_scale();
+ }
+ return p_value;
+ } break;
+#endif // _3D_DISABLED
+ default: {
+ } break;
+ }
+ return p_value;
+}
+
void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started, int p_pingponged) {
_ensure_node_caches(p_anim);
ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count());
@@ -498,6 +515,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (err != OK) {
continue;
}
+ loc = _post_process_key_value(a, i, loc, nc->node_3d, nc->bone_idx);
if (nc->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX);
@@ -525,6 +543,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (err != OK) {
continue;
}
+ rot = _post_process_key_value(a, i, rot, nc->node_3d, nc->bone_idx);
if (nc->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX);
@@ -552,6 +571,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (err != OK) {
continue;
}
+ scale = _post_process_key_value(a, i, scale, nc->node_3d, nc->bone_idx);
if (nc->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX);
@@ -579,6 +599,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (err != OK) {
continue;
}
+ blend = _post_process_key_value(a, i, blend, nc->node_blend_shape, nc->blend_shape_idx);
if (nc->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX);
@@ -631,9 +652,9 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (p_time < first_key_time) {
double c = Math::ease(p_time / first_key_time, transition);
Variant first_value = a->track_get_key_value(i, first_key);
+ first_value = _post_process_key_value(a, i, first_value, nc->node);
Variant interp_value;
Variant::interpolate(pa->capture, first_value, c, interp_value);
-
if (pa->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX);
cache_update_prop[cache_update_prop_size++] = pa;
@@ -653,6 +674,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (value == Variant()) {
continue;
}
+ value = _post_process_key_value(a, i, value, nc->node);
if (pa->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX);
@@ -669,6 +691,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
for (int &F : indices) {
Variant value = a->track_get_key_value(i, F);
+ value = _post_process_key_value(a, i, value, nc->node);
switch (pa->special) {
case SP_NONE: {
bool valid;
@@ -753,6 +776,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
TrackNodeCache::BezierAnim *ba = &E->value;
real_t bezier = a->bezier_track_interpolate(i, p_time);
+ bezier = _post_process_key_value(a, i, bezier, nc->node);
if (ba->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX);
cache_update_bezier[cache_update_bezier_size++] = ba;
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 0d3ce5977e..4dd4b43145 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -316,6 +316,8 @@ protected:
static void _bind_methods();
+ virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1);
+
public:
StringName find_animation(const Ref<Animation> &p_animation) const;
StringName find_animation_library(const Ref<Animation> &p_animation) const;
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 4b80f571ae..b73fd6a24f 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -1054,7 +1054,9 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx);
a->position_track_interpolate(i, (double)a->get_length(), &loc[1]);
+ loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx);
t->loc += (loc[1] - loc[0]) * blend;
prev_time = 0;
}
@@ -1064,7 +1066,9 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx);
a->position_track_interpolate(i, 0, &loc[1]);
+ loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx);
t->loc += (loc[1] - loc[0]) * blend;
prev_time = (double)a->get_length();
}
@@ -1074,8 +1078,10 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx);
a->position_track_interpolate(i, time, &loc[1]);
+ loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx);
t->loc += (loc[1] - loc[0]) * blend;
prev_time = !backward ? 0 : (double)a->get_length();
@@ -1092,6 +1098,7 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ loc = _post_process_key_value(a, i, loc, t->object, t->bone_idx);
t->loc += (loc - t->init_loc) * blend;
}
@@ -1150,7 +1157,9 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx);
a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]);
+ rot[1] = _post_process_key_value(a, i, rot[1], t->object, t->bone_idx);
t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = 0;
}
@@ -1160,6 +1169,7 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx);
a->rotation_track_interpolate(i, 0, &rot[1]);
t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = (double)a->get_length();
@@ -1170,8 +1180,10 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx);
a->rotation_track_interpolate(i, time, &rot[1]);
+ rot[1] = _post_process_key_value(a, i, rot[1], t->object, t->bone_idx);
t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = !backward ? 0 : (double)a->get_length();
@@ -1188,6 +1200,7 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ rot = _post_process_key_value(a, i, rot, t->object, t->bone_idx);
t->rot = (t->rot * Quaternion().slerp(t->init_rot.inverse() * rot, blend)).normalized();
}
@@ -1246,8 +1259,10 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx);
a->scale_track_interpolate(i, (double)a->get_length(), &scale[1]);
t->scale += (scale[1] - scale[0]) * blend;
+ scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx);
prev_time = 0;
}
} else {
@@ -1256,7 +1271,9 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx);
a->scale_track_interpolate(i, 0, &scale[1]);
+ scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx);
t->scale += (scale[1] - scale[0]) * blend;
prev_time = (double)a->get_length();
}
@@ -1266,8 +1283,10 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx);
a->scale_track_interpolate(i, time, &scale[1]);
+ scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx);
t->scale += (scale[1] - scale[0]) * blend;
prev_time = !backward ? 0 : (double)a->get_length();
@@ -1284,6 +1303,7 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ scale = _post_process_key_value(a, i, scale, t->object, t->bone_idx);
t->scale += (scale - t->init_scale) * blend;
}
@@ -1306,6 +1326,7 @@ void AnimationTree::_process_graph(double p_delta) {
if (err != OK) {
continue;
}
+ value = _post_process_key_value(a, i, value, t->object, t->shape_index);
t->value += (value - t->init_value) * blend;
#endif // _3D_DISABLED
@@ -1317,6 +1338,7 @@ void AnimationTree::_process_graph(double p_delta) {
if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) {
Variant value = a->value_track_interpolate(i, time);
+ value = _post_process_key_value(a, i, value, t->object);
if (value == Variant()) {
continue;
@@ -1344,12 +1366,14 @@ void AnimationTree::_process_graph(double p_delta) {
continue;
}
Variant value = a->track_get_key_value(i, idx);
+ value = _post_process_key_value(a, i, value, t->object);
t->object->set_indexed(t->subpath, value);
} else {
List<int> indices;
a->value_track_get_key_indices(i, time, delta, &indices, pingponged);
for (int &F : indices) {
Variant value = a->track_get_key_value(i, F);
+ value = _post_process_key_value(a, i, value, t->object);
t->object->set_indexed(t->subpath, value);
}
}
@@ -1388,6 +1412,7 @@ void AnimationTree::_process_graph(double p_delta) {
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
real_t bezier = a->bezier_track_interpolate(i, time);
+ bezier = _post_process_key_value(a, i, bezier, t->object);
if (t->process_pass != process_pass) {
t->process_pass = process_pass;
@@ -1663,6 +1688,23 @@ void AnimationTree::_process_graph(double p_delta) {
}
}
+Variant AnimationTree::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) {
+ switch (p_anim->track_get_type(p_track)) {
+#ifndef _3D_DISABLED
+ case Animation::TYPE_POSITION_3D: {
+ if (p_object_idx >= 0) {
+ const Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_object);
+ return Vector3(p_value) * skel->get_motion_scale();
+ }
+ return p_value;
+ } break;
+#endif // _3D_DISABLED
+ default: {
+ } break;
+ }
+ return p_value;
+}
+
void AnimationTree::advance(real_t p_time) {
_process_graph(p_time);
}
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index d82fed9dc2..99851d140e 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -321,6 +321,8 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+ virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1);
+
public:
void set_tree_root(const Ref<AnimationNode> &p_root);
Ref<AnimationNode> get_tree_root() const;
diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp
index 0714de470c..bfb4bb6e2b 100644
--- a/scene/resources/skeleton_profile.cpp
+++ b/scene/resources/skeleton_profile.cpp
@@ -123,12 +123,20 @@ bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
void SkeletonProfile::_validate_property(PropertyInfo &property) const {
if (is_read_only) {
- if (property.name == ("group_size") || property.name == ("bone_size")) {
+ if (property.name == ("group_size") || property.name == ("bone_size") || property.name == ("root_bone") || property.name == ("scale_base_bone")) {
property.usage = PROPERTY_USAGE_NO_EDITOR;
return;
}
}
+ if (property.name == ("root_bone") || property.name == ("scale_base_bone")) {
+ String hint = "";
+ for (int i = 0; i < bones.size(); i++) {
+ hint += i == 0 ? String(bones[i].bone_name) : "," + String(bones[i].bone_name);
+ }
+ property.hint_string = hint;
+ }
+
PackedStringArray split = property.name.split("/");
if (split.size() == 3 && split[0] == "bones") {
if (split[2] == "bone_tail" && get_tail_direction(split[1].to_int()) != TAIL_DIRECTION_SPECIFIC_CHILD) {
@@ -168,6 +176,28 @@ void SkeletonProfile::_get_property_list(List<PropertyInfo> *p_list) const {
}
}
+StringName SkeletonProfile::get_root_bone() {
+ return root_bone;
+}
+
+void SkeletonProfile::set_root_bone(StringName p_bone_name) {
+ if (is_read_only) {
+ return;
+ }
+ root_bone = p_bone_name;
+}
+
+StringName SkeletonProfile::get_scale_base_bone() {
+ return scale_base_bone;
+}
+
+void SkeletonProfile::set_scale_base_bone(StringName p_bone_name) {
+ if (is_read_only) {
+ return;
+ }
+ scale_base_bone = p_bone_name;
+}
+
int SkeletonProfile::get_group_size() {
return groups.size();
}
@@ -361,6 +391,12 @@ bool SkeletonProfile::has_bone(StringName p_bone_name) {
}
void SkeletonProfile::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_root_bone", "bone_name"), &SkeletonProfile::set_root_bone);
+ ClassDB::bind_method(D_METHOD("get_root_bone"), &SkeletonProfile::get_root_bone);
+
+ ClassDB::bind_method(D_METHOD("set_scale_base_bone", "bone_name"), &SkeletonProfile::set_scale_base_bone);
+ ClassDB::bind_method(D_METHOD("get_scale_base_bone"), &SkeletonProfile::get_scale_base_bone);
+
ClassDB::bind_method(D_METHOD("set_group_size", "size"), &SkeletonProfile::set_group_size);
ClassDB::bind_method(D_METHOD("get_group_size"), &SkeletonProfile::get_group_size);
@@ -396,6 +432,9 @@ void SkeletonProfile::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_group", "bone_idx"), &SkeletonProfile::get_group);
ClassDB::bind_method(D_METHOD("set_group", "bone_idx", "group"), &SkeletonProfile::set_group);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "root_bone", PROPERTY_HINT_ENUM_SUGGESTION, ""), "set_root_bone", "get_root_bone");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "scale_base_bone", PROPERTY_HINT_ENUM_SUGGESTION, ""), "set_scale_base_bone", "get_scale_base_bone");
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "group_size", PROPERTY_HINT_RANGE, "0,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Groups,groups/"), "set_group_size", "get_group_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_size", PROPERTY_HINT_RANGE, "0,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Bones,bones/"), "set_bone_size", "get_bone_size");
@@ -415,6 +454,9 @@ SkeletonProfile::~SkeletonProfile() {
SkeletonProfileHumanoid::SkeletonProfileHumanoid() {
is_read_only = true;
+ root_bone = "Root";
+ scale_base_bone = "Hips";
+
groups.resize(4);
groups.write[0].group_name = "Body";
diff --git a/scene/resources/skeleton_profile.h b/scene/resources/skeleton_profile.h
index d305311538..84dfca458e 100644
--- a/scene/resources/skeleton_profile.h
+++ b/scene/resources/skeleton_profile.h
@@ -64,6 +64,9 @@ protected:
bool require = false;
};
+ StringName root_bone;
+ StringName scale_base_bone;
+
Vector<SkeletonProfileGroup> groups;
Vector<SkeletonProfileBone> bones;
@@ -74,6 +77,12 @@ protected:
static void _bind_methods();
public:
+ StringName get_root_bone();
+ void set_root_bone(StringName p_bone_name);
+
+ StringName get_scale_base_bone();
+ void set_scale_base_bone(StringName p_bone_name);
+
int get_group_size();
void set_group_size(int p_size);