From f3af3aedfe7ed72ecf6671439ebe00e03f86ed8c Mon Sep 17 00:00:00 2001
From: Silc Renew <tokage.it.lab@gmail.com>
Date: Tue, 5 Jul 2022 14:04:17 +0900
Subject: [PATCH] add rest fixer to importer retarget

---
 doc/classes/SkeletonProfile.xml               |  79 ++++
 .../post_import_plugin_skeleton_renamer.cpp   |  34 ++
 ...post_import_plugin_skeleton_rest_fixer.cpp | 418 ++++++++++++++++++
 .../post_import_plugin_skeleton_rest_fixer.h  |  46 ++
 editor/plugins/bone_map_editor_plugin.cpp     |  29 +-
 editor/plugins/bone_map_editor_plugin.h       |   5 +-
 editor/plugins/skeleton_3d_editor_plugin.cpp  |  78 ++++
 editor/plugins/skeleton_3d_editor_plugin.h    |   3 +
 scene/3d/skeleton_3d.cpp                      |   4 +-
 scene/resources/bone_map.cpp                  |   9 +
 scene/resources/bone_map.h                    |   1 +
 scene/resources/skeleton_profile.cpp          | 311 ++++++++++++-
 scene/resources/skeleton_profile.h            |  31 ++
 13 files changed, 1021 insertions(+), 27 deletions(-)
 create mode 100644 editor/import/post_import_plugin_skeleton_rest_fixer.cpp
 create mode 100644 editor/import/post_import_plugin_skeleton_rest_fixer.h

diff --git a/doc/classes/SkeletonProfile.xml b/doc/classes/SkeletonProfile.xml
index 55a2ea67599..a7f5f7a0a6e 100644
--- a/doc/classes/SkeletonProfile.xml
+++ b/doc/classes/SkeletonProfile.xml
@@ -9,6 +9,13 @@
 	<tutorials>
 	</tutorials>
 	<methods>
+		<method name="find_bone" qualifiers="const">
+			<return type="int" />
+			<argument index="0" name="bone_name" type="StringName" />
+			<description>
+				Returns the bone index that matches [code]bone_name[/code] as its name.
+			</description>
+		</method>
 		<method name="get_bone_name" qualifiers="const">
 			<return type="StringName" />
 			<argument index="0" name="bone_idx" type="int" />
@@ -17,6 +24,20 @@
 				In the retargeting process, the returned bone name is the bone name of the target skeleton.
 			</description>
 		</method>
+		<method name="get_bone_parent" qualifiers="const">
+			<return type="StringName" />
+			<argument index="0" name="bone_idx" type="int" />
+			<description>
+				Returns the name of the bone which is the parent to the bone at [code]bone_idx[/code]. The result is empty if the bone has no parent.
+			</description>
+		</method>
+		<method name="get_bone_tail" qualifiers="const">
+			<return type="StringName" />
+			<argument index="0" name="bone_idx" type="int" />
+			<description>
+				Returns the name of the bone which is the tail of the bone at [code]bone_idx[/code].
+			</description>
+		</method>
 		<method name="get_group" qualifiers="const">
 			<return type="StringName" />
 			<argument index="0" name="bone_idx" type="int" />
@@ -39,6 +60,20 @@
 				This is the offset with origin at the top left corner of the square.
 			</description>
 		</method>
+		<method name="get_reference_pose" qualifiers="const">
+			<return type="Transform3D" />
+			<argument index="0" name="bone_idx" type="int" />
+			<description>
+				Returns the reference pose transform for bone [code]bone_idx[/code].
+			</description>
+		</method>
+		<method name="get_tail_direction" qualifiers="const">
+			<return type="int" enum="SkeletonProfile.TailDirection" />
+			<argument index="0" name="bone_idx" type="int" />
+			<description>
+				Returns the tail direction of the bone at [code]bone_idx[/code].
+			</description>
+		</method>
 		<method name="get_texture" qualifiers="const">
 			<return type="Texture2D" />
 			<argument index="0" name="group_idx" type="int" />
@@ -55,6 +90,22 @@
 				In the retargeting process, the setting bone name is the bone name of the target skeleton.
 			</description>
 		</method>
+		<method name="set_bone_parent">
+			<return type="void" />
+			<argument index="0" name="bone_idx" type="int" />
+			<argument index="1" name="bone_parent" type="StringName" />
+			<description>
+				Sets the bone with name [code]bone_parent[/code] as the parent of the bone at [code]bone_idx[/code]. If an empty string is passed, then the bone has no parent.
+			</description>
+		</method>
+		<method name="set_bone_tail">
+			<return type="void" />
+			<argument index="0" name="bone_idx" type="int" />
+			<argument index="1" name="bone_tail" type="StringName" />
+			<description>
+				Sets the bone with name [code]bone_tail[/code] as the tail of the bone at [code]bone_idx[/code].
+			</description>
+		</method>
 		<method name="set_group">
 			<return type="void" />
 			<argument index="0" name="bone_idx" type="int" />
@@ -80,6 +131,23 @@
 				This is the offset with origin at the top left corner of the square.
 			</description>
 		</method>
+		<method name="set_reference_pose">
+			<return type="void" />
+			<argument index="0" name="bone_idx" type="int" />
+			<argument index="1" name="bone_name" type="Transform3D" />
+			<description>
+				Sets the reference pose transform for bone [code]bone_idx[/code].
+			</description>
+		</method>
+		<method name="set_tail_direction">
+			<return type="void" />
+			<argument index="0" name="bone_idx" type="int" />
+			<argument index="1" name="tail_direction" type="int" enum="SkeletonProfile.TailDirection" />
+			<description>
+				Sets the tail direction of the bone at [code]bone_idx[/code].
+				[b]Note:[/b] This only specifies the method of calculation. The actual coordinates required should be stored in an external skeleton, so the calculation itself needs to be done externally.
+			</description>
+		</method>
 		<method name="set_texture">
 			<return type="void" />
 			<argument index="0" name="group_idx" type="int" />
@@ -103,4 +171,15 @@
 			</description>
 		</signal>
 	</signals>
+	<constants>
+		<constant name="TAIL_DIRECTION_AVERAGE_CHILDREN" value="0" enum="TailDirection">
+			Direction to the average coordinates of bone children.
+		</constant>
+		<constant name="TAIL_DIRECTION_SPECIFIC_CHILD" value="1" enum="TailDirection">
+			Direction to the coordinates of specified bone child.
+		</constant>
+		<constant name="TAIL_DIRECTION_END" value="2" enum="TailDirection">
+			Direction is not calculated.
+		</constant>
+	</constants>
 </class>
diff --git a/editor/import/post_import_plugin_skeleton_renamer.cpp b/editor/import/post_import_plugin_skeleton_renamer.cpp
index b0c4bc8c303..bf84348ac31 100644
--- a/editor/import/post_import_plugin_skeleton_renamer.cpp
+++ b/editor/import/post_import_plugin_skeleton_renamer.cpp
@@ -39,6 +39,8 @@
 void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
 	if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
 		r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones"), true));
+		r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/unique_node/make_unique"), true));
+		r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::STRING, "retarget/bone_renamer/unique_node/skeleton_name"), "GeneralSkeleton"));
 	}
 }
 
@@ -137,6 +139,38 @@ void PostImportPluginSkeletonRenamer::internal_process(InternalImportCategory p_
 				nd->callp("_notify_skeleton_bones_renamed", argptrs, argcount, ce);
 			}
 		}
+
+		// Make unique skeleton.
+		if (bool(p_options["retarget/bone_renamer/unique_node/make_unique"])) {
+			String unique_name = String(p_options["retarget/bone_renamer/unique_node/skeleton_name"]);
+			ERR_FAIL_COND_MSG(unique_name == String(), "Skeleton unique name cannot be empty.");
+
+			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 || 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 == skeleton) {
+								anim->track_set_path(i, String("%") + unique_name + String(":") + anim->track_get_path(i).get_concatenated_subnames());
+							}
+						}
+					}
+				}
+			}
+			skeleton->set_name(unique_name);
+			skeleton->set_unique_name_in_owner(true);
+		}
 	}
 }
 
diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
new file mode 100644
index 00000000000..8b0d8c8729e
--- /dev/null
+++ b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
@@ -0,0 +1,418 @@
+/*************************************************************************/
+/*  post_import_plugin_skeleton_rest_fixer.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_rest_fixer.h"
+
+#include "editor/import/scene_import_settings.h"
+#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/overwrite_axis"), true));
+
+		r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/fix_silhouette/enable"), false));
+		r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/threshold"), 15));
+
+		// TODO: PostImportPlugin need to be implemented such as validate_option(PropertyInfo &property, const Dictionary &p_options).
+		// get_internal_option_visibility() is not sufficient because it can only retrieve options implemented in the core and can only read option values.
+		// r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::STRING_NAME, PROPERTY_HINT_ENUM, "Hips,Spine,Chest")), Array()));
+		r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, "StringName"), Array()));
+	}
+}
+
+void PostImportPluginSkeletonRestFixer::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 is_renamed = bool(p_options["retarget/bone_renamer/rename_bones"]);
+		Array filter = p_options["retarget/rest_fixer/fix_silhouette/filter"];
+		bool is_rest_changed = false;
+
+		// Build profile skeleton.
+		Skeleton3D *prof_skeleton = memnew(Skeleton3D);
+		{
+			int prof_bone_len = profile->get_bone_size();
+			// Add single bones.
+			for (int i = 0; i < prof_bone_len; i++) {
+				prof_skeleton->add_bone(profile->get_bone_name(i));
+				prof_skeleton->set_bone_rest(i, profile->get_reference_pose(i));
+			}
+			// Set parents.
+			for (int i = 0; i < prof_bone_len; i++) {
+				int parent = profile->find_bone(profile->get_bone_parent(i));
+				if (parent >= 0) {
+					prof_skeleton->set_bone_parent(i, parent);
+				}
+			}
+		}
+
+		// Complement Rotation track for compatibility between defference rests.
+		{
+			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();
+
+					// Detect does the animetion have skeleton's TRS track.
+					String track_path;
+					bool found_skeleton = false;
+					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;
+						}
+						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) {
+								found_skeleton = true;
+								break;
+							}
+						}
+					}
+
+					if (found_skeleton) {
+						// Search and insert rot track if it doesn't exist.
+						for (int prof_idx = 0; prof_idx < prof_skeleton->get_bone_count(); prof_idx++) {
+							String bone_name = is_renamed ? prof_skeleton->get_bone_name(prof_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_idx)));
+							if (bone_name == String()) {
+								continue;
+							}
+							int src_idx = src_skeleton->find_bone(bone_name);
+							if (src_idx == -1) {
+								continue;
+							}
+							String insert_path = track_path + ":" + bone_name;
+							int rot_track = anim->find_track(insert_path, Animation::TYPE_ROTATION_3D);
+							if (rot_track == -1) {
+								int track = anim->add_track(Animation::TYPE_ROTATION_3D);
+								anim->track_set_path(track, insert_path);
+								anim->rotation_track_insert_key(track, 0, src_skeleton->get_bone_rest(src_idx).basis.get_rotation_quaternion());
+							}
+						}
+					}
+				}
+			}
+		}
+
+		// Fix silhouette.
+		Vector<Transform3D> silhouette_diff; // Transform values to be ignored when overwrite axis.
+		silhouette_diff.resize(src_skeleton->get_bone_count());
+		Transform3D *silhouette_diff_w = silhouette_diff.ptrw();
+		if (bool(p_options["retarget/rest_fixer/fix_silhouette/enable"])) {
+			LocalVector<Transform3D> old_skeleton_global_rest;
+			for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
+				old_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
+			}
+
+			Vector<int> bones_to_process = prof_skeleton->get_parentless_bones();
+			while (bones_to_process.size() > 0) {
+				int prof_idx = bones_to_process[0];
+				bones_to_process.erase(prof_idx);
+				Vector<int> prof_children = prof_skeleton->get_bone_children(prof_idx);
+				for (int i = 0; i < prof_children.size(); i++) {
+					bones_to_process.push_back(prof_children[i]);
+				}
+
+				// Calc virtual/looking direction with origins.
+				bool is_filtered = false;
+				for (int i = 0; i < filter.size(); i++) {
+					if (String(filter[i]) == prof_skeleton->get_bone_name(prof_idx)) {
+						is_filtered = true;
+						break;
+					}
+				}
+				if (is_filtered) {
+					continue;
+				}
+
+				int src_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_idx))));
+				if (src_idx < 0 || profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_END) {
+					continue;
+				}
+				Vector3 prof_tail;
+				Vector3 src_tail;
+				if (profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_AVERAGE_CHILDREN) {
+					PackedInt32Array prof_bone_children = prof_skeleton->get_bone_children(prof_idx);
+					int children_size = prof_bone_children.size();
+					if (children_size == 0) {
+						continue;
+					}
+					bool exist_all_children = true;
+					for (int i = 0; i < children_size; i++) {
+						int prof_child_idx = prof_bone_children[i];
+						int src_child_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_child_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_child_idx))));
+						if (src_child_idx < 0) {
+							exist_all_children = false;
+							break;
+						}
+						prof_tail = prof_tail + prof_skeleton->get_bone_global_rest(prof_child_idx).origin;
+						src_tail = src_tail + src_skeleton->get_bone_global_rest(src_child_idx).origin;
+					}
+					if (!exist_all_children) {
+						continue;
+					}
+					prof_tail = prof_tail / children_size;
+					src_tail = src_tail / children_size;
+				}
+				if (profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_SPECIFIC_CHILD) {
+					int prof_tail_idx = prof_skeleton->find_bone(profile->get_bone_tail(prof_idx));
+					if (prof_tail_idx < 0) {
+						continue;
+					}
+					int src_tail_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_tail_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_tail_idx))));
+					if (src_tail_idx < 0) {
+						continue;
+					}
+					prof_tail = prof_skeleton->get_bone_global_rest(prof_tail_idx).origin;
+					src_tail = src_skeleton->get_bone_global_rest(src_tail_idx).origin;
+				}
+
+				Vector3 prof_head = prof_skeleton->get_bone_global_rest(prof_idx).origin;
+				Vector3 src_head = src_skeleton->get_bone_global_rest(src_idx).origin;
+
+				Vector3 prof_dir = prof_tail - prof_head;
+				Vector3 src_dir = src_tail - src_head;
+
+				// Rotate rest.
+				if (Math::abs(Math::rad2deg(src_dir.angle_to(prof_dir))) > float(p_options["retarget/rest_fixer/fix_silhouette/threshold"])) {
+					// Get rotation difference.
+					Vector3 up_vec; // Need to rotate other than roll axis.
+					switch (Vector3(abs(src_dir.x), abs(src_dir.y), abs(src_dir.z)).min_axis_index()) {
+						case Vector3::AXIS_X: {
+							up_vec = Vector3(1, 0, 0);
+						} break;
+						case Vector3::AXIS_Y: {
+							up_vec = Vector3(0, 1, 0);
+						} break;
+						case Vector3::AXIS_Z: {
+							up_vec = Vector3(0, 0, 1);
+						} break;
+					}
+					Basis src_b;
+					src_b = src_b.looking_at(src_dir, up_vec);
+					Basis prof_b;
+					prof_b = src_b.looking_at(prof_dir, up_vec);
+					if (prof_b.is_equal_approx(Basis())) {
+						continue; // May not need to rotate.
+					}
+					Basis diff_b = prof_b * src_b.inverse();
+
+					// Apply rotation difference as global transform to skeleton.
+					Basis src_pg;
+					int src_parent = src_skeleton->get_bone_parent(src_idx);
+					if (src_parent >= 0) {
+						src_pg = src_skeleton->get_bone_global_rest(src_parent).basis;
+					}
+					Transform3D fixed_rest = Transform3D(src_pg.inverse() * diff_b * src_pg * src_skeleton->get_bone_rest(src_idx).basis, src_skeleton->get_bone_rest(src_idx).origin);
+					src_skeleton->set_bone_rest(src_idx, fixed_rest);
+				}
+			}
+
+			// For skin modification in overwrite rest.
+			for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
+				silhouette_diff_w[i] = old_skeleton_global_rest[i] * src_skeleton->get_bone_global_rest(i).inverse();
+			}
+
+			is_rest_changed = true;
+		}
+
+		// Overwrite axis.
+		if (bool(p_options["retarget/rest_fixer/overwrite_axis"])) {
+			LocalVector<Transform3D> old_skeleton_rest;
+			LocalVector<Transform3D> old_skeleton_global_rest;
+			for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
+				old_skeleton_rest.push_back(src_skeleton->get_bone_rest(i));
+				old_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
+			}
+
+			Vector<Basis> diffs;
+			diffs.resize(src_skeleton->get_bone_count());
+			Basis *diffs_w = diffs.ptrw();
+
+			Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
+			while (bones_to_process.size() > 0) {
+				int src_idx = bones_to_process[0];
+				bones_to_process.erase(src_idx);
+				Vector<int> src_children = src_skeleton->get_bone_children(src_idx);
+				for (int i = 0; i < src_children.size(); i++) {
+					bones_to_process.push_back(src_children[i]);
+				}
+
+				Basis tgt_rot;
+				StringName src_bone_name = is_renamed ? StringName(src_skeleton->get_bone_name(src_idx)) : bone_map->find_profile_bone_name(src_skeleton->get_bone_name(src_idx));
+				if (src_bone_name != StringName()) {
+					Basis src_pg;
+					int src_parent_idx = src_skeleton->get_bone_parent(src_idx);
+					if (src_parent_idx >= 0) {
+						src_pg = src_skeleton->get_bone_global_rest(src_parent_idx).basis;
+					}
+
+					int prof_idx = profile->find_bone(src_bone_name);
+					if (prof_idx >= 0) {
+						tgt_rot = src_pg.inverse() * prof_skeleton->get_bone_global_rest(prof_idx).basis; // Mapped bone uses reference pose.
+					}
+					/*
+					// If there is rest-relative animation, this logic may be work fine, but currently not so...
+					} else {
+						// tgt_rot = src_pg.inverse() * old_skeleton_global_rest[src_idx].basis; // Non-Mapped bone keeps global rest.
+					}
+					*/
+				}
+
+				if (src_skeleton->get_bone_parent(src_idx) >= 0) {
+					diffs_w[src_idx] = tgt_rot.inverse() * diffs[src_skeleton->get_bone_parent(src_idx)] * src_skeleton->get_bone_rest(src_idx).basis;
+				} else {
+					diffs_w[src_idx] = tgt_rot.inverse() * src_skeleton->get_bone_rest(src_idx).basis;
+				}
+
+				Basis diff;
+				if (src_skeleton->get_bone_parent(src_idx) >= 0) {
+					diff = diffs[src_skeleton->get_bone_parent(src_idx)];
+				}
+				src_skeleton->set_bone_rest(src_idx, Transform3D(tgt_rot, diff.xform(src_skeleton->get_bone_rest(src_idx).origin)));
+			}
+
+			// Fix skin.
+			{
+				TypedArray<Node> nodes = p_base_scene->find_children("*", "ImporterMeshInstance3D");
+				while (nodes.size()) {
+					ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(nodes.pop_back());
+					Ref<Skin> skin = mi->get_skin();
+					if (skin.is_valid()) {
+						Node *node = mi->get_node(mi->get_skeleton_path());
+						if (node) {
+							Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
+							if (mesh_skeleton && node == src_skeleton) {
+								int skin_len = skin->get_bind_count();
+								for (int i = 0; i < skin_len; i++) {
+									StringName bn = skin->get_bind_name(i);
+									int bone_idx = src_skeleton->find_bone(bn);
+									if (bone_idx >= 0) {
+										Transform3D new_rest = silhouette_diff[i] * src_skeleton->get_bone_global_rest(bone_idx);
+										skin->set_bind_pose(i, new_rest.inverse());
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			// Fix animation.
+			{
+				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_ROTATION_3D) {
+								continue;
+							}
+
+							if (anim->track_is_compressed(i)) {
+								continue; // TODO: Adopt to compressed track.
+							}
+
+							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 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;
+										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();
+										}
+
+										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);
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			is_rest_changed = true;
+		}
+
+		// Init skeleton pose to new rest.
+		if (is_rest_changed) {
+			for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
+				Transform3D fixed_rest = src_skeleton->get_bone_rest(i);
+				src_skeleton->set_bone_pose_position(i, fixed_rest.origin);
+				src_skeleton->set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion());
+				src_skeleton->set_bone_pose_scale(i, fixed_rest.basis.get_scale());
+			}
+		}
+
+		memdelete(prof_skeleton);
+	}
+}
+
+PostImportPluginSkeletonRestFixer::PostImportPluginSkeletonRestFixer() {
+}
diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.h b/editor/import/post_import_plugin_skeleton_rest_fixer.h
new file mode 100644
index 00000000000..11e9d08e88d
--- /dev/null
+++ b/editor/import/post_import_plugin_skeleton_rest_fixer.h
@@ -0,0 +1,46 @@
+/*************************************************************************/
+/*  post_import_plugin_skeleton_rest_fixer.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_REST_FIXER_H
+#define POST_IMPORT_PLUGIN_SKELETON_REST_FIXER_H
+
+#include "resource_importer_scene.h"
+
+class PostImportPluginSkeletonRestFixer : public EditorScenePostImportPlugin {
+	GDCLASS(PostImportPluginSkeletonRestFixer, 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;
+
+	PostImportPluginSkeletonRestFixer();
+};
+
+#endif // POST_IMPORT_PLUGIN_SKELETON_REST_FIXER_H
diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp
index fffadae3ebb..967a95be9d9 100644
--- a/editor/plugins/bone_map_editor_plugin.cpp
+++ b/editor/plugins/bone_map_editor_plugin.cpp
@@ -32,6 +32,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/scene_import_settings.h"
 
 void BoneMapperButton::fetch_textures() {
@@ -71,6 +72,10 @@ void BoneMapperButton::set_state(BoneMapState p_state) {
 	}
 }
 
+bool BoneMapperButton::is_require() const {
+	return require;
+}
+
 void BoneMapperButton::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
@@ -79,8 +84,9 @@ void BoneMapperButton::_notification(int p_what) {
 	}
 }
 
-BoneMapperButton::BoneMapperButton(const StringName p_profile_bone_name, bool p_selected) {
+BoneMapperButton::BoneMapperButton(const StringName p_profile_bone_name, bool p_require, bool p_selected) {
 	profile_bone_name = p_profile_bone_name;
+	require = p_require;
 	selected = p_selected;
 }
 
@@ -89,7 +95,7 @@ BoneMapperButton::~BoneMapperButton() {
 
 void BoneMapperItem::create_editor() {
 	skeleton_bone_selector = memnew(EditorPropertyTextEnum);
-	skeleton_bone_selector->setup(skeleton_bone_names);
+	skeleton_bone_selector->setup(skeleton_bone_names, false, true);
 	skeleton_bone_selector->set_label(profile_bone_name);
 	skeleton_bone_selector->set_selectable(false);
 	skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name));
@@ -251,7 +257,7 @@ void BoneMapper::recreate_editor() {
 
 	for (int i = 0; i < len; i++) {
 		if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {
-			BoneMapperButton *mb = memnew(BoneMapperButton(profile->get_bone_name(i), current_bone_idx == i));
+			BoneMapperButton *mb = memnew(BoneMapperButton(profile->get_bone_name(i), profile->is_require(i), current_bone_idx == i));
 			mb->connect("pressed", callable_mp(this, &BoneMapper::set_current_bone_idx), varray(i), CONNECT_DEFERRED);
 			mb->set_h_grow_direction(GROW_DIRECTION_BOTH);
 			mb->set_v_grow_direction(GROW_DIRECTION_BOTH);
@@ -284,8 +290,6 @@ void BoneMapper::recreate_items() {
 	Ref<SkeletonProfile> profile = bone_map->get_profile();
 	if (profile.is_valid()) {
 		PackedStringArray skeleton_bone_names;
-		skeleton_bone_names.push_back(String());
-
 		int len = skeleton->get_bone_count();
 		for (int i = 0; i < len; i++) {
 			skeleton_bone_names.push_back(skeleton->get_bone_name(i));
@@ -314,7 +318,11 @@ void BoneMapper::_update_state() {
 				bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
 			}
 		} else {
-			bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_UNSET);
+			if (bone_mapper_buttons[i]->is_require()) {
+				bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
+			} else {
+				bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_UNSET);
+			}
 		}
 	}
 }
@@ -396,9 +404,12 @@ void BoneMapEditor::_notification(int p_what) {
 			create_editors();
 		} break;
 		case NOTIFICATION_EXIT_TREE: {
+			if (!bone_mapper) {
+				return;
+			}
 			remove_child(bone_mapper);
 			bone_mapper->queue_delete();
-		}
+		} break;
 	}
 }
 
@@ -436,4 +447,8 @@ BoneMapEditorPlugin::BoneMapEditorPlugin() {
 	Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer;
 	post_import_plugin_renamer.instantiate();
 	add_scene_post_import_plugin(post_import_plugin_renamer);
+
+	Ref<PostImportPluginSkeletonRestFixer> post_import_plugin_rest_fixer;
+	post_import_plugin_rest_fixer.instantiate();
+	add_scene_post_import_plugin(post_import_plugin_rest_fixer);
 }
diff --git a/editor/plugins/bone_map_editor_plugin.h b/editor/plugins/bone_map_editor_plugin.h
index 0ec9f74373b..e1ea6b40607 100644
--- a/editor/plugins/bone_map_editor_plugin.h
+++ b/editor/plugins/bone_map_editor_plugin.h
@@ -53,6 +53,7 @@ public:
 private:
 	StringName profile_bone_name;
 	bool selected = false;
+	bool require = false;
 
 	TextureRect *circle;
 
@@ -65,7 +66,9 @@ public:
 	StringName get_profile_bone_name() const;
 	void set_state(BoneMapState p_state);
 
-	BoneMapperButton(const StringName p_profile_bone_name, bool p_selected);
+	bool is_require() const;
+
+	BoneMapperButton(const StringName p_profile_bone_name, bool p_require, bool p_selected);
 	~BoneMapperButton();
 };
 
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 8845fe9eca2..93e44c8ca00 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -42,6 +42,7 @@
 #include "scene/3d/mesh_instance_3d.h"
 #include "scene/3d/physics_body_3d.h"
 #include "scene/resources/capsule_shape_3d.h"
+#include "scene/resources/skeleton_profile.h"
 #include "scene/resources/sphere_shape_3d.h"
 #include "scene/resources/surface_tool.h"
 
@@ -250,6 +251,10 @@ void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
 			create_physical_skeleton();
 			break;
 		}
+		case SKELETON_OPTION_EXPORT_SKELETON_PROFILE: {
+			export_skeleton_profile();
+			break;
+		}
 	}
 }
 
@@ -451,6 +456,73 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi
 	return physical_bone;
 }
 
+void Skeleton3DEditor::export_skeleton_profile() {
+	file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
+	file_dialog->set_title(TTR("Export Skeleton Profile As..."));
+
+	List<String> exts;
+	ResourceLoader::get_recognized_extensions_for_type("SkeletonProfile", &exts);
+	file_dialog->clear_filters();
+	for (const String &K : exts) {
+		file_dialog->add_filter("*." + K);
+	}
+
+	file_dialog->popup_file_dialog();
+}
+
+void Skeleton3DEditor::_file_selected(const String &p_file) {
+	// Export SkeletonProfile.
+	Ref<SkeletonProfile> sp(memnew(SkeletonProfile));
+
+	// Build SkeletonProfile.
+	sp->set_group_size(1);
+
+	Vector<Vector2> handle_positions;
+	Vector2 position_max;
+	Vector2 position_min;
+
+	int len = skeleton->get_bone_count();
+	sp->set_bone_size(len);
+	for (int i = 0; i < len; i++) {
+		sp->set_bone_name(i, skeleton->get_bone_name(i));
+		int parent = skeleton->get_bone_parent(i);
+		if (parent >= 0) {
+			sp->set_bone_parent(i, skeleton->get_bone_name(parent));
+		}
+		sp->set_reference_pose(i, skeleton->get_bone_rest(i));
+
+		Transform3D grest = skeleton->get_bone_global_rest(i);
+		handle_positions.append(Vector2(grest.origin.x, grest.origin.y));
+		if (i == 0) {
+			position_max = Vector2(grest.origin.x, grest.origin.y);
+			position_min = Vector2(grest.origin.x, grest.origin.y);
+		} else {
+			position_max.x = MAX(grest.origin.x, position_max.x);
+			position_max.y = MAX(grest.origin.y, position_max.y);
+			position_min.x = MIN(grest.origin.x, position_min.x);
+			position_min.y = MIN(grest.origin.y, position_min.y);
+		}
+	}
+
+	// Layout handles provisionaly.
+	Vector2 bound = Vector2(position_max.x - position_min.x, position_max.y - position_min.y);
+	Vector2 center = Vector2((position_max.x + position_min.x) * 0.5, (position_max.y + position_min.y) * 0.5);
+	float nrm = MAX(bound.x, bound.y);
+	if (nrm > 0) {
+		for (int i = 0; i < len; i++) {
+			handle_positions.write[i] = (handle_positions[i] - center) / nrm * 0.9;
+			sp->set_handle_offset(i, Vector2(0.5 + handle_positions[i].x, 0.5 - handle_positions[i].y));
+		}
+	}
+
+	Error err = ResourceSaver::save(p_file, sp);
+
+	if (err != OK) {
+		EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s"), p_file));
+		return;
+	}
+}
+
 Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
 	TreeItem *selected = joint_tree->get_selected();
 
@@ -631,6 +703,11 @@ void Skeleton3DEditor::create_editors() {
 	Node3DEditor *ne = Node3DEditor::get_singleton();
 	AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
 
+	// Create File dialog.
+	file_dialog = memnew(EditorFileDialog);
+	file_dialog->connect("file_selected", callable_mp(this, &Skeleton3DEditor::_file_selected));
+	add_child(file_dialog);
+
 	// Create Top Menu Bar.
 	separator = memnew(VSeparator);
 	ne->add_control_to_menu_panel(separator);
@@ -649,6 +726,7 @@ void Skeleton3DEditor::create_editors() {
 	p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/all_poses_to_rests", TTR("Apply all poses to rests")), SKELETON_OPTION_ALL_POSES_TO_RESTS);
 	p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/selected_poses_to_rests", TTR("Apply selected poses to rests")), SKELETON_OPTION_SELECTED_POSES_TO_RESTS);
 	p->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON);
+	p->add_item(TTR("Export skeleton profile"), SKELETON_OPTION_EXPORT_SKELETON_PROFILE);
 
 	p->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option));
 	set_bone_options_enabled(false);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 8f03e7c8db5..975b54fa77e 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -101,6 +101,7 @@ class Skeleton3DEditor : public VBoxContainer {
 		SKELETON_OPTION_ALL_POSES_TO_RESTS,
 		SKELETON_OPTION_SELECTED_POSES_TO_RESTS,
 		SKELETON_OPTION_CREATE_PHYSICAL_SKELETON,
+		SKELETON_OPTION_EXPORT_SKELETON_PROFILE,
 	};
 
 	struct BoneInfo {
@@ -155,6 +156,8 @@ class Skeleton3DEditor : public VBoxContainer {
 	void create_physical_skeleton();
 	PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos);
 
+	void export_skeleton_profile();
+
 	Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
 	bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
 	void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index fbd5f31dd58..b342660b859 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -639,6 +639,7 @@ void Skeleton3D::remove_bone_child(int p_bone, int p_child) {
 }
 
 Vector<int> Skeleton3D::get_parentless_bones() {
+	_update_process_order();
 	return parentless_bones;
 }
 
@@ -765,8 +766,6 @@ void Skeleton3D::_make_dirty() {
 }
 
 void Skeleton3D::localize_rests() {
-	_update_process_order();
-
 	Vector<int> bones_to_process = get_parentless_bones();
 	while (bones_to_process.size() > 0) {
 		int current_bone_idx = bones_to_process[0];
@@ -958,7 +957,6 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
 
 	skin.instantiate();
 	skin->set_bind_count(bones.size());
-	_update_process_order(); // Just in case.
 
 	// Pose changed, rebuild cache of inverses.
 	const Bone *bonesptr = bones.ptr();
diff --git a/scene/resources/bone_map.cpp b/scene/resources/bone_map.cpp
index ce030934fa6..aff917b2d4f 100644
--- a/scene/resources/bone_map.cpp
+++ b/scene/resources/bone_map.cpp
@@ -50,6 +50,14 @@ bool BoneMap::_get(const StringName &p_path, Variant &r_ret) const {
 	return true;
 }
 
+void BoneMap::_get_property_list(List<PropertyInfo> *p_list) const {
+	HashMap<StringName, StringName>::ConstIterator E = bone_map.begin();
+	while (E) {
+		p_list->push_back(PropertyInfo(Variant::STRING_NAME, "bone_map/" + E->key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+		++E;
+	}
+}
+
 Ref<SkeletonProfile> BoneMap::get_profile() const {
 	return profile;
 }
@@ -153,6 +161,7 @@ void BoneMap::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("find_profile_bone_name", "skeleton_bone_name"), &BoneMap::find_profile_bone_name);
 
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "profile", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonProfile"), "set_profile", "get_profile");
+	ADD_ARRAY("bonemap", "bonemap");
 
 	ADD_SIGNAL(MethodInfo("bone_map_updated"));
 	ADD_SIGNAL(MethodInfo("profile_updated"));
diff --git a/scene/resources/bone_map.h b/scene/resources/bone_map.h
index 4b7928015db..17452dfc734 100644
--- a/scene/resources/bone_map.h
+++ b/scene/resources/bone_map.h
@@ -46,6 +46,7 @@ protected:
 	bool _get(const StringName &p_path, Variant &r_ret) const;
 	bool _set(const StringName &p_path, const Variant &p_value);
 	virtual void _validate_property(PropertyInfo &property) const override;
+	void _get_property_list(List<PropertyInfo> *p_list) const;
 	static void _bind_methods();
 
 public:
diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp
index 05d48f9545c..0714de470c5 100644
--- a/scene/resources/skeleton_profile.cpp
+++ b/scene/resources/skeleton_profile.cpp
@@ -34,7 +34,7 @@ bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) {
 	ERR_FAIL_COND_V(is_read_only, false);
 	String path = p_path;
 
-	if (path.begins_with("group/")) {
+	if (path.begins_with("groups/")) {
 		int which = path.get_slicec('/', 1).to_int();
 		String what = path.get_slicec('/', 2);
 		ERR_FAIL_INDEX_V(which, groups.size(), false);
@@ -43,23 +43,35 @@ bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) {
 			set_group_name(which, p_value);
 		} else if (what == "texture") {
 			set_texture(which, p_value);
+		} else {
+			return false;
 		}
-		return true;
 	}
 
-	if (path.begins_with("bone/")) {
+	if (path.begins_with("bones/")) {
 		int which = path.get_slicec('/', 1).to_int();
 		String what = path.get_slicec('/', 2);
 		ERR_FAIL_INDEX_V(which, bones.size(), false);
 
 		if (what == "bone_name") {
 			set_bone_name(which, p_value);
+		} else if (what == "bone_parent") {
+			set_bone_parent(which, p_value);
+		} else if (what == "tail_direction") {
+			set_tail_direction(which, static_cast<TailDirection>((int)p_value));
+		} else if (what == "bone_tail") {
+			set_bone_tail(which, p_value);
+		} else if (what == "reference_pose") {
+			set_reference_pose(which, p_value);
 		} else if (what == "handle_offset") {
 			set_handle_offset(which, p_value);
 		} else if (what == "group") {
 			set_group(which, p_value);
+		} else if (what == "require") {
+			set_require(which, p_value);
+		} else {
+			return false;
 		}
-		return true;
 	}
 	return true;
 }
@@ -67,7 +79,7 @@ bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) {
 bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
 	String path = p_path;
 
-	if (path.begins_with("group/")) {
+	if (path.begins_with("groups/")) {
 		int which = path.get_slicec('/', 1).to_int();
 		String what = path.get_slicec('/', 2);
 		ERR_FAIL_INDEX_V(which, groups.size(), false);
@@ -76,23 +88,35 @@ bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
 			r_ret = get_group_name(which);
 		} else if (what == "texture") {
 			r_ret = get_texture(which);
+		} else {
+			return false;
 		}
-		return true;
 	}
 
-	if (path.begins_with("bone/")) {
+	if (path.begins_with("bones/")) {
 		int which = path.get_slicec('/', 1).to_int();
 		String what = path.get_slicec('/', 2);
 		ERR_FAIL_INDEX_V(which, bones.size(), false);
 
 		if (what == "bone_name") {
 			r_ret = get_bone_name(which);
+		} else if (what == "bone_parent") {
+			r_ret = get_bone_parent(which);
+		} else if (what == "tail_direction") {
+			r_ret = get_tail_direction(which);
+		} else if (what == "bone_tail") {
+			r_ret = get_bone_tail(which);
+		} else if (what == "reference_pose") {
+			r_ret = get_reference_pose(which);
 		} else if (what == "handle_offset") {
 			r_ret = get_handle_offset(which);
 		} else if (what == "group") {
 			r_ret = get_group(which);
+		} else if (what == "require") {
+			r_ret = is_require(which);
+		} else {
+			return false;
 		}
-		return true;
 	}
 	return true;
 }
@@ -104,6 +128,13 @@ void SkeletonProfile::_validate_property(PropertyInfo &property) const {
 			return;
 		}
 	}
+
+	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) {
+			property.usage = PROPERTY_USAGE_NONE;
+		}
+	}
 }
 
 void SkeletonProfile::_get_property_list(List<PropertyInfo> *p_list) const {
@@ -112,7 +143,7 @@ void SkeletonProfile::_get_property_list(List<PropertyInfo> *p_list) const {
 	}
 	String group_names = "";
 	for (int i = 0; i < groups.size(); i++) {
-		String path = "group/" + itos(i) + "/";
+		String path = "groups/" + itos(i) + "/";
 		p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "group_name"));
 		p_list->push_back(PropertyInfo(Variant::OBJECT, path + "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"));
 		if (i > 0) {
@@ -121,10 +152,19 @@ void SkeletonProfile::_get_property_list(List<PropertyInfo> *p_list) const {
 		group_names = group_names + groups[i].group_name;
 	}
 	for (int i = 0; i < bones.size(); i++) {
-		String path = "bone/" + itos(i) + "/";
+		String path = "bones/" + itos(i) + "/";
 		p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "bone_name"));
+		p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "bone_parent"));
+		p_list->push_back(PropertyInfo(Variant::INT, path + "tail_direction", PROPERTY_HINT_ENUM, "AverageChildren,SpecificChild,End"));
+		p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "bone_tail"));
+		p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, path + "reference_pose"));
 		p_list->push_back(PropertyInfo(Variant::VECTOR2, path + "handle_offset"));
 		p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "group", PROPERTY_HINT_ENUM, group_names));
+		p_list->push_back(PropertyInfo(Variant::BOOL, path + "require"));
+	}
+
+	for (PropertyInfo &E : *p_list) {
+		_validate_property(E);
 	}
 }
 
@@ -184,6 +224,18 @@ void SkeletonProfile::set_bone_size(int p_size) {
 	notify_property_list_changed();
 }
 
+int SkeletonProfile::find_bone(StringName p_bone_name) const {
+	if (p_bone_name == StringName()) {
+		return -1;
+	}
+	for (int i = 0; i < bones.size(); i++) {
+		if (bones[i].bone_name == p_bone_name) {
+			return i;
+		}
+	}
+	return -1;
+}
+
 StringName SkeletonProfile::get_bone_name(int p_bone_idx) const {
 	ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName());
 	return bones[p_bone_idx].bone_name;
@@ -198,6 +250,63 @@ void SkeletonProfile::set_bone_name(int p_bone_idx, const StringName p_bone_name
 	emit_signal("profile_updated");
 }
 
+StringName SkeletonProfile::get_bone_parent(int p_bone_idx) const {
+	ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName());
+	return bones[p_bone_idx].bone_parent;
+}
+
+void SkeletonProfile::set_bone_parent(int p_bone_idx, const StringName p_bone_parent) {
+	if (is_read_only) {
+		return;
+	}
+	ERR_FAIL_INDEX(p_bone_idx, bones.size());
+	bones.write[p_bone_idx].bone_parent = p_bone_parent;
+	emit_signal("profile_updated");
+}
+
+SkeletonProfile::TailDirection SkeletonProfile::get_tail_direction(int p_bone_idx) const {
+	ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), TAIL_DIRECTION_AVERAGE_CHILDREN);
+	return bones[p_bone_idx].tail_direction;
+}
+
+void SkeletonProfile::set_tail_direction(int p_bone_idx, const TailDirection p_tail_direction) {
+	if (is_read_only) {
+		return;
+	}
+	ERR_FAIL_INDEX(p_bone_idx, bones.size());
+	bones.write[p_bone_idx].tail_direction = p_tail_direction;
+	emit_signal("profile_updated");
+	notify_property_list_changed();
+}
+
+StringName SkeletonProfile::get_bone_tail(int p_bone_idx) const {
+	ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName());
+	return bones[p_bone_idx].bone_tail;
+}
+
+void SkeletonProfile::set_bone_tail(int p_bone_idx, const StringName p_bone_tail) {
+	if (is_read_only) {
+		return;
+	}
+	ERR_FAIL_INDEX(p_bone_idx, bones.size());
+	bones.write[p_bone_idx].bone_tail = p_bone_tail;
+	emit_signal("profile_updated");
+}
+
+Transform3D SkeletonProfile::get_reference_pose(int p_bone_idx) const {
+	ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Transform3D());
+	return bones[p_bone_idx].reference_pose;
+}
+
+void SkeletonProfile::set_reference_pose(int p_bone_idx, const Transform3D p_reference_pose) {
+	if (is_read_only) {
+		return;
+	}
+	ERR_FAIL_INDEX(p_bone_idx, bones.size());
+	bones.write[p_bone_idx].reference_pose = p_reference_pose;
+	emit_signal("profile_updated");
+}
+
 Vector2 SkeletonProfile::get_handle_offset(int p_bone_idx) const {
 	ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector2());
 	return bones[p_bone_idx].handle_offset;
@@ -226,6 +335,20 @@ void SkeletonProfile::set_group(int p_bone_idx, const StringName p_group) {
 	emit_signal("profile_updated");
 }
 
+bool SkeletonProfile::is_require(int p_bone_idx) const {
+	ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), false);
+	return bones[p_bone_idx].require;
+}
+
+void SkeletonProfile::set_require(int p_bone_idx, const bool p_require) {
+	if (is_read_only) {
+		return;
+	}
+	ERR_FAIL_INDEX(p_bone_idx, bones.size());
+	bones.write[p_bone_idx].require = p_require;
+	emit_signal("profile_updated");
+}
+
 bool SkeletonProfile::has_bone(StringName p_bone_name) {
 	bool is_found = false;
 	for (int i = 0; i < bones.size(); i++) {
@@ -250,19 +373,37 @@ void SkeletonProfile::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_bone_size", "size"), &SkeletonProfile::set_bone_size);
 	ClassDB::bind_method(D_METHOD("get_bone_size"), &SkeletonProfile::get_bone_size);
 
+	ClassDB::bind_method(D_METHOD("find_bone", "bone_name"), &SkeletonProfile::find_bone);
+
 	ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &SkeletonProfile::get_bone_name);
 	ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "bone_name"), &SkeletonProfile::set_bone_name);
 
+	ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &SkeletonProfile::get_bone_parent);
+	ClassDB::bind_method(D_METHOD("set_bone_parent", "bone_idx", "bone_parent"), &SkeletonProfile::set_bone_parent);
+
+	ClassDB::bind_method(D_METHOD("get_tail_direction", "bone_idx"), &SkeletonProfile::get_tail_direction);
+	ClassDB::bind_method(D_METHOD("set_tail_direction", "bone_idx", "tail_direction"), &SkeletonProfile::set_tail_direction);
+
+	ClassDB::bind_method(D_METHOD("get_bone_tail", "bone_idx"), &SkeletonProfile::get_bone_tail);
+	ClassDB::bind_method(D_METHOD("set_bone_tail", "bone_idx", "bone_tail"), &SkeletonProfile::set_bone_tail);
+
+	ClassDB::bind_method(D_METHOD("get_reference_pose", "bone_idx"), &SkeletonProfile::get_reference_pose);
+	ClassDB::bind_method(D_METHOD("set_reference_pose", "bone_idx", "bone_name"), &SkeletonProfile::set_reference_pose);
+
 	ClassDB::bind_method(D_METHOD("get_handle_offset", "bone_idx"), &SkeletonProfile::get_handle_offset);
 	ClassDB::bind_method(D_METHOD("set_handle_offset", "bone_idx", "handle_offset"), &SkeletonProfile::set_handle_offset);
 
 	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::INT, "group_size", PROPERTY_HINT_RANGE, "0,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Groups,group/"), "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,bone/"), "set_bone_size", "get_bone_size");
+	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");
 
 	ADD_SIGNAL(MethodInfo("profile_updated"));
+
+	BIND_ENUM_CONSTANT(TAIL_DIRECTION_AVERAGE_CHILDREN);
+	BIND_ENUM_CONSTANT(TAIL_DIRECTION_SPECIFIC_CHILD);
+	BIND_ENUM_CONSTANT(TAIL_DIRECTION_END);
 }
 
 SkeletonProfile::SkeletonProfile() {
@@ -284,226 +425,364 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() {
 	bones.resize(56);
 
 	bones.write[0].bone_name = "Root";
+	bones.write[0].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0);
 	bones.write[0].handle_offset = Vector2(0.5, 0.91);
 	bones.write[0].group = "Body";
 
 	bones.write[1].bone_name = "Hips";
+	bones.write[1].bone_parent = "Root";
+	bones.write[1].tail_direction = TAIL_DIRECTION_SPECIFIC_CHILD;
+	bones.write[1].bone_tail = "Spine";
+	bones.write[1].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0);
 	bones.write[1].handle_offset = Vector2(0.5, 0.5);
 	bones.write[1].group = "Body";
+	bones.write[1].require = true;
 
 	bones.write[2].bone_name = "Spine";
+	bones.write[2].bone_parent = "Hips";
+	bones.write[2].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0);
 	bones.write[2].handle_offset = Vector2(0.5, 0.43);
 	bones.write[2].group = "Body";
+	bones.write[2].require = true;
 
 	bones.write[3].bone_name = "Chest";
+	bones.write[3].bone_parent = "Spine";
+	bones.write[3].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0);
 	bones.write[3].handle_offset = Vector2(0.5, 0.36);
 	bones.write[3].group = "Body";
 
 	bones.write[4].bone_name = "UpperChest";
+	bones.write[4].bone_parent = "Chest";
+	bones.write[4].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0);
 	bones.write[4].handle_offset = Vector2(0.5, 0.29);
 	bones.write[4].group = "Body";
 
 	bones.write[5].bone_name = "Neck";
+	bones.write[5].bone_parent = "UpperChest";
+	bones.write[5].tail_direction = TAIL_DIRECTION_SPECIFIC_CHILD;
+	bones.write[5].bone_tail = "Head";
+	bones.write[5].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0);
 	bones.write[5].handle_offset = Vector2(0.5, 0.23);
 	bones.write[5].group = "Body";
+	bones.write[5].require = true;
 
 	bones.write[6].bone_name = "Head";
+	bones.write[6].bone_parent = "Neck";
+	bones.write[6].tail_direction = TAIL_DIRECTION_END;
+	bones.write[6].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0);
 	bones.write[6].handle_offset = Vector2(0.5, 0.18);
 	bones.write[6].group = "Body";
+	bones.write[6].require = true;
 
 	bones.write[7].bone_name = "LeftEye";
+	bones.write[7].bone_parent = "Head";
+	bones.write[7].reference_pose = Transform3D(1, 0, 0, 0, 0, -1, 0, 1, 0, 0.05, 0.15, 0);
 	bones.write[7].handle_offset = Vector2(0.6, 0.46);
 	bones.write[7].group = "Face";
 
 	bones.write[8].bone_name = "RightEye";
+	bones.write[8].bone_parent = "Head";
+	bones.write[8].reference_pose = Transform3D(1, 0, 0, 0, 0, -1, 0, 1, 0, -0.05, 0.15, 0);
 	bones.write[8].handle_offset = Vector2(0.37, 0.46);
 	bones.write[8].group = "Face";
 
 	bones.write[9].bone_name = "Jaw";
+	bones.write[9].bone_parent = "Head";
+	bones.write[9].reference_pose = Transform3D(-1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0.05, 0.05);
 	bones.write[9].handle_offset = Vector2(0.46, 0.75);
 	bones.write[9].group = "Face";
 
 	bones.write[10].bone_name = "LeftShoulder";
+	bones.write[10].bone_parent = "UpperChest";
+	bones.write[10].reference_pose = Transform3D(0, 1, 0, 0, 0, 1, 1, 0, 0, 0.05, 0.1, 0);
 	bones.write[10].handle_offset = Vector2(0.55, 0.235);
 	bones.write[10].group = "Body";
+	bones.write[10].require = true;
 
 	bones.write[11].bone_name = "LeftUpperArm";
+	bones.write[11].bone_parent = "LeftShoulder";
+	bones.write[11].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.05, 0);
 	bones.write[11].handle_offset = Vector2(0.6, 0.24);
 	bones.write[11].group = "Body";
+	bones.write[11].require = true;
 
 	bones.write[12].bone_name = "LeftLowerArm";
+	bones.write[12].bone_parent = "LeftUpperArm";
+	bones.write[12].reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0, 0.25, 0);
 	bones.write[12].handle_offset = Vector2(0.7, 0.24);
 	bones.write[12].group = "Body";
+	bones.write[12].require = true;
 
 	bones.write[13].bone_name = "LeftHand";
+	bones.write[13].bone_parent = "LeftLowerArm";
+	bones.write[13].tail_direction = TAIL_DIRECTION_SPECIFIC_CHILD;
+	bones.write[13].bone_tail = "LeftMiddleProximal";
+	bones.write[13].reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0, 0.25, 0);
 	bones.write[13].handle_offset = Vector2(0.82, 0.235);
 	bones.write[13].group = "Body";
+	bones.write[13].require = true;
 
-	bones.write[14].bone_name = "LeftThumbProximal";
+	bones.write[14].bone_name = "LeftThumbMetacarpal";
+	bones.write[14].bone_parent = "LeftHand";
+	bones.write[14].reference_pose = Transform3D(0, -0.577, 0.816, 0.707, 0.577, 0.408, -0.707, 0.577, 0.408, -0.025, 0, 0);
 	bones.write[14].handle_offset = Vector2(0.4, 0.8);
 	bones.write[14].group = "LeftHand";
 
-	bones.write[15].bone_name = "LeftThumbIntermediate";
+	bones.write[15].bone_name = "LeftThumbProximal";
+	bones.write[15].bone_parent = "LeftThumbMetacarpal";
+	bones.write[15].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.043, 0);
 	bones.write[15].handle_offset = Vector2(0.3, 0.69);
 	bones.write[15].group = "LeftHand";
 
 	bones.write[16].bone_name = "LeftThumbDistal";
+	bones.write[16].bone_parent = "LeftThumbProximal";
+	bones.write[16].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.043, 0);
 	bones.write[16].handle_offset = Vector2(0.23, 0.555);
 	bones.write[16].group = "LeftHand";
 
 	bones.write[17].bone_name = "LeftIndexProximal";
+	bones.write[17].bone_parent = "LeftHand";
+	bones.write[17].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.025, 0.075, 0);
 	bones.write[17].handle_offset = Vector2(0.413, 0.52);
 	bones.write[17].group = "LeftHand";
 
 	bones.write[18].bone_name = "LeftIndexIntermediate";
+	bones.write[18].bone_parent = "LeftIndexProximal";
+	bones.write[18].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0);
 	bones.write[18].handle_offset = Vector2(0.403, 0.36);
 	bones.write[18].group = "LeftHand";
 
 	bones.write[19].bone_name = "LeftIndexDistal";
+	bones.write[19].bone_parent = "LeftIndexIntermediate";
+	bones.write[19].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0);
 	bones.write[19].handle_offset = Vector2(0.403, 0.255);
 	bones.write[19].group = "LeftHand";
 
 	bones.write[20].bone_name = "LeftMiddleProximal";
+	bones.write[20].bone_parent = "LeftHand";
+	bones.write[20].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0);
 	bones.write[20].handle_offset = Vector2(0.5, 0.51);
 	bones.write[20].group = "LeftHand";
 
 	bones.write[21].bone_name = "LeftMiddleIntermediate";
+	bones.write[21].bone_parent = "LeftMiddleProximal";
+	bones.write[21].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0);
 	bones.write[21].handle_offset = Vector2(0.5, 0.345);
 	bones.write[21].group = "LeftHand";
 
 	bones.write[22].bone_name = "LeftMiddleDistal";
+	bones.write[22].bone_parent = "LeftMiddleIntermediate";
+	bones.write[22].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0);
 	bones.write[22].handle_offset = Vector2(0.5, 0.22);
 	bones.write[22].group = "LeftHand";
 
 	bones.write[23].bone_name = "LeftRingProximal";
+	bones.write[23].bone_parent = "LeftHand";
+	bones.write[23].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.025, 0.075, 0);
 	bones.write[23].handle_offset = Vector2(0.586, 0.52);
 	bones.write[23].group = "LeftHand";
 
 	bones.write[24].bone_name = "LeftRingIntermediate";
+	bones.write[24].bone_parent = "LeftRingProximal";
+	bones.write[24].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0);
 	bones.write[24].handle_offset = Vector2(0.59, 0.36);
 	bones.write[24].group = "LeftHand";
 
 	bones.write[25].bone_name = "LeftRingDistal";
+	bones.write[25].bone_parent = "LeftRingIntermediate";
+	bones.write[25].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0);
 	bones.write[25].handle_offset = Vector2(0.591, 0.25);
 	bones.write[25].group = "LeftHand";
 
 	bones.write[26].bone_name = "LeftLittleProximal";
+	bones.write[26].bone_parent = "LeftHand";
+	bones.write[26].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.05, 0.05, 0);
 	bones.write[26].handle_offset = Vector2(0.663, 0.543);
 	bones.write[26].group = "LeftHand";
 
 	bones.write[27].bone_name = "LeftLittleIntermediate";
+	bones.write[27].bone_parent = "LeftLittleProximal";
+	bones.write[27].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0);
 	bones.write[27].handle_offset = Vector2(0.672, 0.415);
 	bones.write[27].group = "LeftHand";
 
 	bones.write[28].bone_name = "LeftLittleDistal";
+	bones.write[28].bone_parent = "LeftLittleIntermediate";
+	bones.write[28].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0);
 	bones.write[28].handle_offset = Vector2(0.672, 0.32);
 	bones.write[28].group = "LeftHand";
 
 	bones.write[29].bone_name = "RightShoulder";
+	bones.write[29].bone_parent = "UpperChest";
+	bones.write[29].reference_pose = Transform3D(0, -1, 0, 0, 0, 1, -1, 0, 0, -0.05, 0.1, 0);
 	bones.write[29].handle_offset = Vector2(0.45, 0.235);
 	bones.write[29].group = "Body";
+	bones.write[29].require = true;
 
 	bones.write[30].bone_name = "RightUpperArm";
+	bones.write[30].bone_parent = "RightShoulder";
+	bones.write[30].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.05, 0);
 	bones.write[30].handle_offset = Vector2(0.4, 0.24);
 	bones.write[30].group = "Body";
+	bones.write[30].require = true;
 
 	bones.write[31].bone_name = "RightLowerArm";
+	bones.write[31].bone_parent = "RightUpperArm";
+	bones.write[31].reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0, 0.25, 0);
 	bones.write[31].handle_offset = Vector2(0.3, 0.24);
 	bones.write[31].group = "Body";
+	bones.write[31].require = true;
 
 	bones.write[32].bone_name = "RightHand";
+	bones.write[32].bone_parent = "RightLowerArm";
+	bones.write[32].tail_direction = TAIL_DIRECTION_SPECIFIC_CHILD;
+	bones.write[32].bone_tail = "RightMiddleProximal";
+	bones.write[32].reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0, 0.25, 0);
 	bones.write[32].handle_offset = Vector2(0.18, 0.235);
 	bones.write[32].group = "Body";
+	bones.write[32].require = true;
 
-	bones.write[33].bone_name = "RightThumbProximal";
+	bones.write[33].bone_name = "RightThumbMetacarpal";
+	bones.write[33].bone_parent = "RightHand";
+	bones.write[33].reference_pose = Transform3D(0, 0.577, -0.816, -0.707, 0.577, 0.408, 0.707, 0.577, 0.408, 0.025, 0, 0);
 	bones.write[33].handle_offset = Vector2(0.6, 0.8);
 	bones.write[33].group = "RightHand";
 
-	bones.write[34].bone_name = "RightThumbIntermediate";
+	bones.write[34].bone_name = "RightThumbProximal";
+	bones.write[34].bone_parent = "RightThumbMetacarpal";
+	bones.write[34].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.043, 0);
 	bones.write[34].handle_offset = Vector2(0.7, 0.69);
 	bones.write[34].group = "RightHand";
 
 	bones.write[35].bone_name = "RightThumbDistal";
+	bones.write[35].bone_parent = "RightThumbProximal";
+	bones.write[35].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.043, 0);
 	bones.write[35].handle_offset = Vector2(0.77, 0.555);
 	bones.write[35].group = "RightHand";
 
 	bones.write[36].bone_name = "RightIndexProximal";
+	bones.write[36].bone_parent = "RightHand";
+	bones.write[36].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.025, 0.075, 0);
 	bones.write[36].handle_offset = Vector2(0.587, 0.52);
 	bones.write[36].group = "RightHand";
 
 	bones.write[37].bone_name = "RightIndexIntermediate";
+	bones.write[37].bone_parent = "RightIndexProximal";
+	bones.write[37].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0);
 	bones.write[37].handle_offset = Vector2(0.597, 0.36);
 	bones.write[37].group = "RightHand";
 
 	bones.write[38].bone_name = "RightIndexDistal";
+	bones.write[38].bone_parent = "RightIndexIntermediate";
+	bones.write[38].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0);
 	bones.write[38].handle_offset = Vector2(0.597, 0.255);
 	bones.write[38].group = "RightHand";
 
 	bones.write[39].bone_name = "RightMiddleProximal";
+	bones.write[39].bone_parent = "RightHand";
+	bones.write[39].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0);
 	bones.write[39].handle_offset = Vector2(0.5, 0.51);
 	bones.write[39].group = "RightHand";
 
 	bones.write[40].bone_name = "RightMiddleIntermediate";
+	bones.write[40].bone_parent = "RightMiddleProximal";
+	bones.write[40].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0);
 	bones.write[40].handle_offset = Vector2(0.5, 0.345);
 	bones.write[40].group = "RightHand";
 
 	bones.write[41].bone_name = "RightMiddleDistal";
+	bones.write[41].bone_parent = "RightMiddleIntermediate";
+	bones.write[41].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0);
 	bones.write[41].handle_offset = Vector2(0.5, 0.22);
 	bones.write[41].group = "RightHand";
 
 	bones.write[42].bone_name = "RightRingProximal";
+	bones.write[42].bone_parent = "RightHand";
+	bones.write[42].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.025, 0.075, 0);
 	bones.write[42].handle_offset = Vector2(0.414, 0.52);
 	bones.write[42].group = "RightHand";
 
 	bones.write[43].bone_name = "RightRingIntermediate";
+	bones.write[43].bone_parent = "RightRingProximal";
+	bones.write[43].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0);
 	bones.write[43].handle_offset = Vector2(0.41, 0.36);
 	bones.write[43].group = "RightHand";
 
 	bones.write[44].bone_name = "RightRingDistal";
+	bones.write[44].bone_parent = "RightRingIntermediate";
+	bones.write[44].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0);
 	bones.write[44].handle_offset = Vector2(0.409, 0.25);
 	bones.write[44].group = "RightHand";
 
 	bones.write[45].bone_name = "RightLittleProximal";
+	bones.write[45].bone_parent = "RightHand";
+	bones.write[45].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.05, 0.05, 0);
 	bones.write[45].handle_offset = Vector2(0.337, 0.543);
 	bones.write[45].group = "RightHand";
 
 	bones.write[46].bone_name = "RightLittleIntermediate";
+	bones.write[46].bone_parent = "RightLittleProximal";
+	bones.write[46].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0);
 	bones.write[46].handle_offset = Vector2(0.328, 0.415);
 	bones.write[46].group = "RightHand";
 
 	bones.write[47].bone_name = "RightLittleDistal";
+	bones.write[47].bone_parent = "RightLittleIntermediate";
+	bones.write[47].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0);
 	bones.write[47].handle_offset = Vector2(0.328, 0.32);
 	bones.write[47].group = "RightHand";
 
 	bones.write[48].bone_name = "LeftUpperLeg";
+	bones.write[48].bone_parent = "Hips";
+	bones.write[48].reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, 0.1, 0, 0);
 	bones.write[48].handle_offset = Vector2(0.549, 0.49);
 	bones.write[48].group = "Body";
+	bones.write[48].require = true;
 
 	bones.write[49].bone_name = "LeftLowerLeg";
+	bones.write[49].bone_parent = "LeftUpperLeg";
+	bones.write[49].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.375, 0);
 	bones.write[49].handle_offset = Vector2(0.548, 0.683);
 	bones.write[49].group = "Body";
+	bones.write[49].require = true;
 
 	bones.write[50].bone_name = "LeftFoot";
+	bones.write[50].bone_parent = "LeftLowerLeg";
+	bones.write[50].reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0.375, 0);
 	bones.write[50].handle_offset = Vector2(0.545, 0.9);
 	bones.write[50].group = "Body";
+	bones.write[50].require = true;
 
 	bones.write[51].bone_name = "LeftToes";
+	bones.write[51].bone_parent = "LeftFoot";
+	bones.write[51].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.15, 0);
 	bones.write[51].handle_offset = Vector2(0.545, 0.95);
 	bones.write[51].group = "Body";
 
 	bones.write[52].bone_name = "RightUpperLeg";
+	bones.write[52].bone_parent = "Hips";
+	bones.write[52].reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, -0.1, 0, 0);
 	bones.write[52].handle_offset = Vector2(0.451, 0.49);
 	bones.write[52].group = "Body";
+	bones.write[52].require = true;
 
 	bones.write[53].bone_name = "RightLowerLeg";
+	bones.write[53].bone_parent = "RightUpperLeg";
+	bones.write[53].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.375, 0);
 	bones.write[53].handle_offset = Vector2(0.452, 0.683);
 	bones.write[53].group = "Body";
+	bones.write[53].require = true;
 
 	bones.write[54].bone_name = "RightFoot";
+	bones.write[54].bone_parent = "RightLowerLeg";
+	bones.write[54].reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0.375, 0);
 	bones.write[54].handle_offset = Vector2(0.455, 0.9);
 	bones.write[54].group = "Body";
+	bones.write[54].require = true;
 
 	bones.write[55].bone_name = "RightToes";
+	bones.write[55].bone_parent = "RightFoot";
+	bones.write[55].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.15, 0);
 	bones.write[55].handle_offset = Vector2(0.455, 0.95);
 	bones.write[55].group = "Body";
 }
diff --git a/scene/resources/skeleton_profile.h b/scene/resources/skeleton_profile.h
index 920aaa2b8d1..d305311538b 100644
--- a/scene/resources/skeleton_profile.h
+++ b/scene/resources/skeleton_profile.h
@@ -36,6 +36,13 @@
 class SkeletonProfile : public Resource {
 	GDCLASS(SkeletonProfile, Resource);
 
+public:
+	enum TailDirection {
+		TAIL_DIRECTION_AVERAGE_CHILDREN,
+		TAIL_DIRECTION_SPECIFIC_CHILD,
+		TAIL_DIRECTION_END
+	};
+
 protected:
 	// Note: SkeletonProfileHumanoid which extends SkeletonProfile exists to unify standard bone names.
 	// That is what is_read_only is for, so don't make it public.
@@ -48,8 +55,13 @@ protected:
 
 	struct SkeletonProfileBone {
 		StringName bone_name;
+		StringName bone_parent;
+		TailDirection tail_direction = TAIL_DIRECTION_AVERAGE_CHILDREN;
+		StringName bone_tail;
+		Transform3D reference_pose;
 		Vector2 handle_offset;
 		StringName group;
+		bool require = false;
 	};
 
 	Vector<SkeletonProfileGroup> groups;
@@ -74,15 +86,32 @@ public:
 	int get_bone_size();
 	void set_bone_size(int p_size);
 
+	int find_bone(const StringName p_bone_name) const;
+
 	StringName get_bone_name(int p_bone_idx) const;
 	void set_bone_name(int p_bone_idx, const StringName p_bone_name);
 
+	StringName get_bone_parent(int p_bone_idx) const;
+	void set_bone_parent(int p_bone_idx, const StringName p_bone_parent);
+
+	TailDirection get_tail_direction(int p_bone_idx) const;
+	void set_tail_direction(int p_bone_idx, const TailDirection p_tail_direction);
+
+	StringName get_bone_tail(int p_bone_idx) const;
+	void set_bone_tail(int p_bone_idx, const StringName p_bone_tail);
+
+	Transform3D get_reference_pose(int p_bone_idx) const;
+	void set_reference_pose(int p_bone_idx, const Transform3D p_reference_pose);
+
 	Vector2 get_handle_offset(int p_bone_idx) const;
 	void set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset);
 
 	StringName get_group(int p_bone_idx) const;
 	void set_group(int p_bone_idx, const StringName p_group);
 
+	bool is_require(int p_bone_idx) const;
+	void set_require(int p_bone_idx, const bool p_require);
+
 	bool has_bone(StringName p_bone_name);
 
 	SkeletonProfile();
@@ -97,4 +126,6 @@ public:
 	~SkeletonProfileHumanoid();
 };
 
+VARIANT_ENUM_CAST(SkeletonProfile::TailDirection);
+
 #endif // SKELETON_PROFILE_H