Merge pull request #62939 from TokageItLab/implement-rest-fixer

Add Rest Fixer to importer retarget
This commit is contained in:
Rémi Verschelde 2022-07-16 16:54:20 +02:00 committed by GitHub
commit 5a6b13b8bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1021 additions and 27 deletions

View File

@ -9,6 +9,13 @@
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <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"> <method name="get_bone_name" qualifiers="const">
<return type="StringName" /> <return type="StringName" />
<argument index="0" name="bone_idx" type="int" /> <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. In the retargeting process, the returned bone name is the bone name of the target skeleton.
</description> </description>
</method> </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"> <method name="get_group" qualifiers="const">
<return type="StringName" /> <return type="StringName" />
<argument index="0" name="bone_idx" type="int" /> <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. This is the offset with origin at the top left corner of the square.
</description> </description>
</method> </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"> <method name="get_texture" qualifiers="const">
<return type="Texture2D" /> <return type="Texture2D" />
<argument index="0" name="group_idx" type="int" /> <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. In the retargeting process, the setting bone name is the bone name of the target skeleton.
</description> </description>
</method> </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"> <method name="set_group">
<return type="void" /> <return type="void" />
<argument index="0" name="bone_idx" type="int" /> <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. This is the offset with origin at the top left corner of the square.
</description> </description>
</method> </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"> <method name="set_texture">
<return type="void" /> <return type="void" />
<argument index="0" name="group_idx" type="int" /> <argument index="0" name="group_idx" type="int" />
@ -103,4 +171,15 @@
</description> </description>
</signal> </signal>
</signals> </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> </class>

View File

@ -39,6 +39,8 @@
void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) { void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) { if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones"), true)); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones"), 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); 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);
}
} }
} }

View File

@ -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() {
}

View File

@ -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

View File

@ -32,6 +32,7 @@
#include "editor/editor_scale.h" #include "editor/editor_scale.h"
#include "editor/import/post_import_plugin_skeleton_renamer.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" #include "editor/import/scene_import_settings.h"
void BoneMapperButton::fetch_textures() { 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) { void BoneMapperButton::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_ENTER_TREE: { 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; profile_bone_name = p_profile_bone_name;
require = p_require;
selected = p_selected; selected = p_selected;
} }
@ -89,7 +95,7 @@ BoneMapperButton::~BoneMapperButton() {
void BoneMapperItem::create_editor() { void BoneMapperItem::create_editor() {
skeleton_bone_selector = memnew(EditorPropertyTextEnum); 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_label(profile_bone_name);
skeleton_bone_selector->set_selectable(false); skeleton_bone_selector->set_selectable(false);
skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name)); 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++) { for (int i = 0; i < len; i++) {
if (profile->get_group(i) == profile->get_group_name(current_group_idx)) { 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->connect("pressed", callable_mp(this, &BoneMapper::set_current_bone_idx), varray(i), CONNECT_DEFERRED);
mb->set_h_grow_direction(GROW_DIRECTION_BOTH); mb->set_h_grow_direction(GROW_DIRECTION_BOTH);
mb->set_v_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(); Ref<SkeletonProfile> profile = bone_map->get_profile();
if (profile.is_valid()) { if (profile.is_valid()) {
PackedStringArray skeleton_bone_names; PackedStringArray skeleton_bone_names;
skeleton_bone_names.push_back(String());
int len = skeleton->get_bone_count(); int len = skeleton->get_bone_count();
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
skeleton_bone_names.push_back(skeleton->get_bone_name(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); bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
} }
} else { } 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(); create_editors();
} break; } break;
case NOTIFICATION_EXIT_TREE: { case NOTIFICATION_EXIT_TREE: {
if (!bone_mapper) {
return;
}
remove_child(bone_mapper); remove_child(bone_mapper);
bone_mapper->queue_delete(); bone_mapper->queue_delete();
} } break;
} }
} }
@ -436,4 +447,8 @@ BoneMapEditorPlugin::BoneMapEditorPlugin() {
Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer; Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer;
post_import_plugin_renamer.instantiate(); post_import_plugin_renamer.instantiate();
add_scene_post_import_plugin(post_import_plugin_renamer); 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);
} }

View File

@ -53,6 +53,7 @@ public:
private: private:
StringName profile_bone_name; StringName profile_bone_name;
bool selected = false; bool selected = false;
bool require = false;
TextureRect *circle; TextureRect *circle;
@ -65,7 +66,9 @@ public:
StringName get_profile_bone_name() const; StringName get_profile_bone_name() const;
void set_state(BoneMapState p_state); 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(); ~BoneMapperButton();
}; };

View File

@ -42,6 +42,7 @@
#include "scene/3d/mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/physics_body_3d.h" #include "scene/3d/physics_body_3d.h"
#include "scene/resources/capsule_shape_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/sphere_shape_3d.h"
#include "scene/resources/surface_tool.h" #include "scene/resources/surface_tool.h"
@ -250,6 +251,10 @@ void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
create_physical_skeleton(); create_physical_skeleton();
break; 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; 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) { Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
TreeItem *selected = joint_tree->get_selected(); TreeItem *selected = joint_tree->get_selected();
@ -631,6 +703,11 @@ void Skeleton3DEditor::create_editors() {
Node3DEditor *ne = Node3DEditor::get_singleton(); Node3DEditor *ne = Node3DEditor::get_singleton();
AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); 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. // Create Top Menu Bar.
separator = memnew(VSeparator); separator = memnew(VSeparator);
ne->add_control_to_menu_panel(separator); 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/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_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("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)); p->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option));
set_bone_options_enabled(false); set_bone_options_enabled(false);

View File

@ -101,6 +101,7 @@ class Skeleton3DEditor : public VBoxContainer {
SKELETON_OPTION_ALL_POSES_TO_RESTS, SKELETON_OPTION_ALL_POSES_TO_RESTS,
SKELETON_OPTION_SELECTED_POSES_TO_RESTS, SKELETON_OPTION_SELECTED_POSES_TO_RESTS,
SKELETON_OPTION_CREATE_PHYSICAL_SKELETON, SKELETON_OPTION_CREATE_PHYSICAL_SKELETON,
SKELETON_OPTION_EXPORT_SKELETON_PROFILE,
}; };
struct BoneInfo { struct BoneInfo {
@ -155,6 +156,8 @@ class Skeleton3DEditor : public VBoxContainer {
void create_physical_skeleton(); void create_physical_skeleton();
PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos); 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); 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; 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); void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);

View File

@ -639,6 +639,7 @@ void Skeleton3D::remove_bone_child(int p_bone, int p_child) {
} }
Vector<int> Skeleton3D::get_parentless_bones() { Vector<int> Skeleton3D::get_parentless_bones() {
_update_process_order();
return parentless_bones; return parentless_bones;
} }
@ -765,8 +766,6 @@ void Skeleton3D::_make_dirty() {
} }
void Skeleton3D::localize_rests() { void Skeleton3D::localize_rests() {
_update_process_order();
Vector<int> bones_to_process = get_parentless_bones(); Vector<int> bones_to_process = get_parentless_bones();
while (bones_to_process.size() > 0) { while (bones_to_process.size() > 0) {
int current_bone_idx = bones_to_process[0]; int current_bone_idx = bones_to_process[0];
@ -958,7 +957,6 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
skin.instantiate(); skin.instantiate();
skin->set_bind_count(bones.size()); skin->set_bind_count(bones.size());
_update_process_order(); // Just in case.
// Pose changed, rebuild cache of inverses. // Pose changed, rebuild cache of inverses.
const Bone *bonesptr = bones.ptr(); const Bone *bonesptr = bones.ptr();

View File

@ -50,6 +50,14 @@ bool BoneMap::_get(const StringName &p_path, Variant &r_ret) const {
return true; 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 { Ref<SkeletonProfile> BoneMap::get_profile() const {
return profile; 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); 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_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("bone_map_updated"));
ADD_SIGNAL(MethodInfo("profile_updated")); ADD_SIGNAL(MethodInfo("profile_updated"));

View File

@ -46,6 +46,7 @@ protected:
bool _get(const StringName &p_path, Variant &r_ret) const; bool _get(const StringName &p_path, Variant &r_ret) const;
bool _set(const StringName &p_path, const Variant &p_value); bool _set(const StringName &p_path, const Variant &p_value);
virtual void _validate_property(PropertyInfo &property) const override; virtual void _validate_property(PropertyInfo &property) const override;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods(); static void _bind_methods();
public: public:

View File

@ -34,7 +34,7 @@ bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) {
ERR_FAIL_COND_V(is_read_only, false); ERR_FAIL_COND_V(is_read_only, false);
String path = p_path; String path = p_path;
if (path.begins_with("group/")) { if (path.begins_with("groups/")) {
int which = path.get_slicec('/', 1).to_int(); int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2); String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, groups.size(), false); 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); set_group_name(which, p_value);
} else if (what == "texture") { } else if (what == "texture") {
set_texture(which, p_value); 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(); int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2); String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, bones.size(), false); ERR_FAIL_INDEX_V(which, bones.size(), false);
if (what == "bone_name") { if (what == "bone_name") {
set_bone_name(which, p_value); 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") { } else if (what == "handle_offset") {
set_handle_offset(which, p_value); set_handle_offset(which, p_value);
} else if (what == "group") { } else if (what == "group") {
set_group(which, p_value); set_group(which, p_value);
} else if (what == "require") {
set_require(which, p_value);
} else {
return false;
} }
return true;
} }
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 { bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path; String path = p_path;
if (path.begins_with("group/")) { if (path.begins_with("groups/")) {
int which = path.get_slicec('/', 1).to_int(); int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2); String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, groups.size(), false); 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); r_ret = get_group_name(which);
} else if (what == "texture") { } else if (what == "texture") {
r_ret = get_texture(which); 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(); int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2); String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, bones.size(), false); ERR_FAIL_INDEX_V(which, bones.size(), false);
if (what == "bone_name") { if (what == "bone_name") {
r_ret = get_bone_name(which); 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") { } else if (what == "handle_offset") {
r_ret = get_handle_offset(which); r_ret = get_handle_offset(which);
} else if (what == "group") { } else if (what == "group") {
r_ret = get_group(which); r_ret = get_group(which);
} else if (what == "require") {
r_ret = is_require(which);
} else {
return false;
} }
return true;
} }
return true; return true;
} }
@ -104,6 +128,13 @@ void SkeletonProfile::_validate_property(PropertyInfo &property) const {
return; 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 { 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 = ""; String group_names = "";
for (int i = 0; i < groups.size(); i++) { 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::STRING_NAME, path + "group_name"));
p_list->push_back(PropertyInfo(Variant::OBJECT, path + "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D")); p_list->push_back(PropertyInfo(Variant::OBJECT, path + "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"));
if (i > 0) { 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; group_names = group_names + groups[i].group_name;
} }
for (int i = 0; i < bones.size(); i++) { 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_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::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::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(); 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 { StringName SkeletonProfile::get_bone_name(int p_bone_idx) const {
ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName()); ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName());
return bones[p_bone_idx].bone_name; 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"); 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 { Vector2 SkeletonProfile::get_handle_offset(int p_bone_idx) const {
ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector2()); ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector2());
return bones[p_bone_idx].handle_offset; 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"); 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 SkeletonProfile::has_bone(StringName p_bone_name) {
bool is_found = false; bool is_found = false;
for (int i = 0; i < bones.size(); i++) { 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("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("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("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("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("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("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("get_group", "bone_idx"), &SkeletonProfile::get_group);
ClassDB::bind_method(D_METHOD("set_group", "bone_idx", "group"), &SkeletonProfile::set_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, "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,bone/"), "set_bone_size", "get_bone_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")); 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() { SkeletonProfile::SkeletonProfile() {
@ -284,226 +425,364 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() {
bones.resize(56); bones.resize(56);
bones.write[0].bone_name = "Root"; 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].handle_offset = Vector2(0.5, 0.91);
bones.write[0].group = "Body"; bones.write[0].group = "Body";
bones.write[1].bone_name = "Hips"; 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].handle_offset = Vector2(0.5, 0.5);
bones.write[1].group = "Body"; bones.write[1].group = "Body";
bones.write[1].require = true;
bones.write[2].bone_name = "Spine"; 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].handle_offset = Vector2(0.5, 0.43);
bones.write[2].group = "Body"; bones.write[2].group = "Body";
bones.write[2].require = true;
bones.write[3].bone_name = "Chest"; 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].handle_offset = Vector2(0.5, 0.36);
bones.write[3].group = "Body"; bones.write[3].group = "Body";
bones.write[4].bone_name = "UpperChest"; 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].handle_offset = Vector2(0.5, 0.29);
bones.write[4].group = "Body"; bones.write[4].group = "Body";
bones.write[5].bone_name = "Neck"; 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].handle_offset = Vector2(0.5, 0.23);
bones.write[5].group = "Body"; bones.write[5].group = "Body";
bones.write[5].require = true;
bones.write[6].bone_name = "Head"; 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].handle_offset = Vector2(0.5, 0.18);
bones.write[6].group = "Body"; bones.write[6].group = "Body";
bones.write[6].require = true;
bones.write[7].bone_name = "LeftEye"; 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].handle_offset = Vector2(0.6, 0.46);
bones.write[7].group = "Face"; bones.write[7].group = "Face";
bones.write[8].bone_name = "RightEye"; 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].handle_offset = Vector2(0.37, 0.46);
bones.write[8].group = "Face"; bones.write[8].group = "Face";
bones.write[9].bone_name = "Jaw"; 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].handle_offset = Vector2(0.46, 0.75);
bones.write[9].group = "Face"; bones.write[9].group = "Face";
bones.write[10].bone_name = "LeftShoulder"; 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].handle_offset = Vector2(0.55, 0.235);
bones.write[10].group = "Body"; bones.write[10].group = "Body";
bones.write[10].require = true;
bones.write[11].bone_name = "LeftUpperArm"; 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].handle_offset = Vector2(0.6, 0.24);
bones.write[11].group = "Body"; bones.write[11].group = "Body";
bones.write[11].require = true;
bones.write[12].bone_name = "LeftLowerArm"; 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].handle_offset = Vector2(0.7, 0.24);
bones.write[12].group = "Body"; bones.write[12].group = "Body";
bones.write[12].require = true;
bones.write[13].bone_name = "LeftHand"; 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].handle_offset = Vector2(0.82, 0.235);
bones.write[13].group = "Body"; 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].handle_offset = Vector2(0.4, 0.8);
bones.write[14].group = "LeftHand"; 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].handle_offset = Vector2(0.3, 0.69);
bones.write[15].group = "LeftHand"; bones.write[15].group = "LeftHand";
bones.write[16].bone_name = "LeftThumbDistal"; 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].handle_offset = Vector2(0.23, 0.555);
bones.write[16].group = "LeftHand"; bones.write[16].group = "LeftHand";
bones.write[17].bone_name = "LeftIndexProximal"; 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].handle_offset = Vector2(0.413, 0.52);
bones.write[17].group = "LeftHand"; bones.write[17].group = "LeftHand";
bones.write[18].bone_name = "LeftIndexIntermediate"; 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].handle_offset = Vector2(0.403, 0.36);
bones.write[18].group = "LeftHand"; bones.write[18].group = "LeftHand";
bones.write[19].bone_name = "LeftIndexDistal"; 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].handle_offset = Vector2(0.403, 0.255);
bones.write[19].group = "LeftHand"; bones.write[19].group = "LeftHand";
bones.write[20].bone_name = "LeftMiddleProximal"; 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].handle_offset = Vector2(0.5, 0.51);
bones.write[20].group = "LeftHand"; bones.write[20].group = "LeftHand";
bones.write[21].bone_name = "LeftMiddleIntermediate"; 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].handle_offset = Vector2(0.5, 0.345);
bones.write[21].group = "LeftHand"; bones.write[21].group = "LeftHand";
bones.write[22].bone_name = "LeftMiddleDistal"; 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].handle_offset = Vector2(0.5, 0.22);
bones.write[22].group = "LeftHand"; bones.write[22].group = "LeftHand";
bones.write[23].bone_name = "LeftRingProximal"; 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].handle_offset = Vector2(0.586, 0.52);
bones.write[23].group = "LeftHand"; bones.write[23].group = "LeftHand";
bones.write[24].bone_name = "LeftRingIntermediate"; 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].handle_offset = Vector2(0.59, 0.36);
bones.write[24].group = "LeftHand"; bones.write[24].group = "LeftHand";
bones.write[25].bone_name = "LeftRingDistal"; 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].handle_offset = Vector2(0.591, 0.25);
bones.write[25].group = "LeftHand"; bones.write[25].group = "LeftHand";
bones.write[26].bone_name = "LeftLittleProximal"; 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].handle_offset = Vector2(0.663, 0.543);
bones.write[26].group = "LeftHand"; bones.write[26].group = "LeftHand";
bones.write[27].bone_name = "LeftLittleIntermediate"; 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].handle_offset = Vector2(0.672, 0.415);
bones.write[27].group = "LeftHand"; bones.write[27].group = "LeftHand";
bones.write[28].bone_name = "LeftLittleDistal"; 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].handle_offset = Vector2(0.672, 0.32);
bones.write[28].group = "LeftHand"; bones.write[28].group = "LeftHand";
bones.write[29].bone_name = "RightShoulder"; 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].handle_offset = Vector2(0.45, 0.235);
bones.write[29].group = "Body"; bones.write[29].group = "Body";
bones.write[29].require = true;
bones.write[30].bone_name = "RightUpperArm"; 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].handle_offset = Vector2(0.4, 0.24);
bones.write[30].group = "Body"; bones.write[30].group = "Body";
bones.write[30].require = true;
bones.write[31].bone_name = "RightLowerArm"; 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].handle_offset = Vector2(0.3, 0.24);
bones.write[31].group = "Body"; bones.write[31].group = "Body";
bones.write[31].require = true;
bones.write[32].bone_name = "RightHand"; 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].handle_offset = Vector2(0.18, 0.235);
bones.write[32].group = "Body"; 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].handle_offset = Vector2(0.6, 0.8);
bones.write[33].group = "RightHand"; 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].handle_offset = Vector2(0.7, 0.69);
bones.write[34].group = "RightHand"; bones.write[34].group = "RightHand";
bones.write[35].bone_name = "RightThumbDistal"; 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].handle_offset = Vector2(0.77, 0.555);
bones.write[35].group = "RightHand"; bones.write[35].group = "RightHand";
bones.write[36].bone_name = "RightIndexProximal"; 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].handle_offset = Vector2(0.587, 0.52);
bones.write[36].group = "RightHand"; bones.write[36].group = "RightHand";
bones.write[37].bone_name = "RightIndexIntermediate"; 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].handle_offset = Vector2(0.597, 0.36);
bones.write[37].group = "RightHand"; bones.write[37].group = "RightHand";
bones.write[38].bone_name = "RightIndexDistal"; 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].handle_offset = Vector2(0.597, 0.255);
bones.write[38].group = "RightHand"; bones.write[38].group = "RightHand";
bones.write[39].bone_name = "RightMiddleProximal"; 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].handle_offset = Vector2(0.5, 0.51);
bones.write[39].group = "RightHand"; bones.write[39].group = "RightHand";
bones.write[40].bone_name = "RightMiddleIntermediate"; 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].handle_offset = Vector2(0.5, 0.345);
bones.write[40].group = "RightHand"; bones.write[40].group = "RightHand";
bones.write[41].bone_name = "RightMiddleDistal"; 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].handle_offset = Vector2(0.5, 0.22);
bones.write[41].group = "RightHand"; bones.write[41].group = "RightHand";
bones.write[42].bone_name = "RightRingProximal"; 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].handle_offset = Vector2(0.414, 0.52);
bones.write[42].group = "RightHand"; bones.write[42].group = "RightHand";
bones.write[43].bone_name = "RightRingIntermediate"; 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].handle_offset = Vector2(0.41, 0.36);
bones.write[43].group = "RightHand"; bones.write[43].group = "RightHand";
bones.write[44].bone_name = "RightRingDistal"; 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].handle_offset = Vector2(0.409, 0.25);
bones.write[44].group = "RightHand"; bones.write[44].group = "RightHand";
bones.write[45].bone_name = "RightLittleProximal"; 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].handle_offset = Vector2(0.337, 0.543);
bones.write[45].group = "RightHand"; bones.write[45].group = "RightHand";
bones.write[46].bone_name = "RightLittleIntermediate"; 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].handle_offset = Vector2(0.328, 0.415);
bones.write[46].group = "RightHand"; bones.write[46].group = "RightHand";
bones.write[47].bone_name = "RightLittleDistal"; 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].handle_offset = Vector2(0.328, 0.32);
bones.write[47].group = "RightHand"; bones.write[47].group = "RightHand";
bones.write[48].bone_name = "LeftUpperLeg"; 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].handle_offset = Vector2(0.549, 0.49);
bones.write[48].group = "Body"; bones.write[48].group = "Body";
bones.write[48].require = true;
bones.write[49].bone_name = "LeftLowerLeg"; 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].handle_offset = Vector2(0.548, 0.683);
bones.write[49].group = "Body"; bones.write[49].group = "Body";
bones.write[49].require = true;
bones.write[50].bone_name = "LeftFoot"; 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].handle_offset = Vector2(0.545, 0.9);
bones.write[50].group = "Body"; bones.write[50].group = "Body";
bones.write[50].require = true;
bones.write[51].bone_name = "LeftToes"; 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].handle_offset = Vector2(0.545, 0.95);
bones.write[51].group = "Body"; bones.write[51].group = "Body";
bones.write[52].bone_name = "RightUpperLeg"; 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].handle_offset = Vector2(0.451, 0.49);
bones.write[52].group = "Body"; bones.write[52].group = "Body";
bones.write[52].require = true;
bones.write[53].bone_name = "RightLowerLeg"; 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].handle_offset = Vector2(0.452, 0.683);
bones.write[53].group = "Body"; bones.write[53].group = "Body";
bones.write[53].require = true;
bones.write[54].bone_name = "RightFoot"; 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].handle_offset = Vector2(0.455, 0.9);
bones.write[54].group = "Body"; bones.write[54].group = "Body";
bones.write[54].require = true;
bones.write[55].bone_name = "RightToes"; 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].handle_offset = Vector2(0.455, 0.95);
bones.write[55].group = "Body"; bones.write[55].group = "Body";
} }

View File

@ -36,6 +36,13 @@
class SkeletonProfile : public Resource { class SkeletonProfile : public Resource {
GDCLASS(SkeletonProfile, Resource); GDCLASS(SkeletonProfile, Resource);
public:
enum TailDirection {
TAIL_DIRECTION_AVERAGE_CHILDREN,
TAIL_DIRECTION_SPECIFIC_CHILD,
TAIL_DIRECTION_END
};
protected: protected:
// Note: SkeletonProfileHumanoid which extends SkeletonProfile exists to unify standard bone names. // 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. // That is what is_read_only is for, so don't make it public.
@ -48,8 +55,13 @@ protected:
struct SkeletonProfileBone { struct SkeletonProfileBone {
StringName bone_name; StringName bone_name;
StringName bone_parent;
TailDirection tail_direction = TAIL_DIRECTION_AVERAGE_CHILDREN;
StringName bone_tail;
Transform3D reference_pose;
Vector2 handle_offset; Vector2 handle_offset;
StringName group; StringName group;
bool require = false;
}; };
Vector<SkeletonProfileGroup> groups; Vector<SkeletonProfileGroup> groups;
@ -74,15 +86,32 @@ public:
int get_bone_size(); int get_bone_size();
void set_bone_size(int p_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; StringName get_bone_name(int p_bone_idx) const;
void set_bone_name(int p_bone_idx, const StringName p_bone_name); 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; Vector2 get_handle_offset(int p_bone_idx) const;
void set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset); void set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset);
StringName get_group(int p_bone_idx) const; StringName get_group(int p_bone_idx) const;
void set_group(int p_bone_idx, const StringName p_group); 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); bool has_bone(StringName p_bone_name);
SkeletonProfile(); SkeletonProfile();
@ -97,4 +126,6 @@ public:
~SkeletonProfileHumanoid(); ~SkeletonProfileHumanoid();
}; };
VARIANT_ENUM_CAST(SkeletonProfile::TailDirection);
#endif // SKELETON_PROFILE_H #endif // SKELETON_PROFILE_H