From 607992777fe9326abd9d2cd7325bce71d0b95320 Mon Sep 17 00:00:00 2001 From: Marios Staikopoulos Date: Sun, 13 Oct 2019 13:41:05 -0700 Subject: [PATCH] SkeletonDefinition Implementation Allows Skeletons to share a common definition, as well as export skeletons for future use --- editor/plugins/skeleton_editor_plugin.cpp | 37 ++++- editor/plugins/skeleton_editor_plugin.h | 9 +- scene/3d/skeleton.cpp | 105 ++++++++++++- scene/3d/skeleton.h | 12 +- scene/register_scene_types.cpp | 2 + scene/resources/skeleton_definition.cpp | 176 ++++++++++++++++++++++ scene/resources/skeleton_definition.h | 90 +++++++++++ 7 files changed, 419 insertions(+), 12 deletions(-) create mode 100644 scene/resources/skeleton_definition.cpp create mode 100644 scene/resources/skeleton_definition.h diff --git a/editor/plugins/skeleton_editor_plugin.cpp b/editor/plugins/skeleton_editor_plugin.cpp index cd360d4caf2e..8dc122ec894f 100644 --- a/editor/plugins/skeleton_editor_plugin.cpp +++ b/editor/plugins/skeleton_editor_plugin.cpp @@ -30,10 +30,13 @@ #include "skeleton_editor_plugin.h" +#include "core/io/resource_saver.h" +#include "editor/editor_file_dialog.h" #include "scene/3d/collision_shape.h" #include "scene/3d/physics_body.h" #include "scene/3d/physics_joint.h" #include "scene/resources/capsule_shape.h" +#include "scene/resources/skeleton_definition.h" #include "scene/resources/sphere_shape.h" #include "spatial_editor_plugin.h" @@ -45,7 +48,26 @@ void SkeletonEditor::_on_click_option(int p_option) { switch (p_option) { case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { create_physical_skeleton(); - } break; + break; + } + case MENU_OPTION_SAVE_DEFINITION: { + save_skeleton_definition(); + break; + } + } +} + +void SkeletonEditor::save_skeleton_definition() { + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_dialog->popup_centered_ratio(); +} + +void SkeletonEditor::_file_selected(const String &p_file) { + const Ref def = SkeletonDefinition::create_from_skeleton(skeleton); + const Error result = ResourceSaver::save(p_file, def); + + if (result != Error::OK) { + ERR_FAIL_MSG("Failed to Save the SkeletonDefinition"); } } @@ -132,8 +154,11 @@ void SkeletonEditor::edit(Skeleton *p_node) { } void SkeletonEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - get_tree()->connect("node_removed", this, "_node_removed"); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", this, "_node_removed"); + file_dialog->connect("file_selected", this, "_file_selected"); + } } } @@ -148,6 +173,7 @@ void SkeletonEditor::_node_removed(Node *p_node) { void SkeletonEditor::_bind_methods() { ClassDB::bind_method("_on_click_option", &SkeletonEditor::_on_click_option); ClassDB::bind_method("_node_removed", &SkeletonEditor::_node_removed); + ClassDB::bind_method(D_METHOD("_file_selected"), &SkeletonEditor::_file_selected); } SkeletonEditor::SkeletonEditor() { @@ -159,9 +185,14 @@ SkeletonEditor::SkeletonEditor() { options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Skeleton", "EditorIcons")); options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); + options->get_popup()->add_item(TTR("Export Skeleton Definition"), MENU_OPTION_SAVE_DEFINITION); options->get_popup()->connect("id_pressed", this, "_on_click_option"); options->hide(); + + file_dialog = memnew(EditorFileDialog); + file_dialog->add_filter("*.skel"); + options->add_child(file_dialog); } SkeletonEditor::~SkeletonEditor() {} diff --git a/editor/plugins/skeleton_editor_plugin.h b/editor/plugins/skeleton_editor_plugin.h index 558e95481571..4544fa0b024f 100644 --- a/editor/plugins/skeleton_editor_plugin.h +++ b/editor/plugins/skeleton_editor_plugin.h @@ -37,12 +37,14 @@ class PhysicalBone; class Joint; +class EditorFileDialog; class SkeletonEditor : public Node { GDCLASS(SkeletonEditor, Node); enum Menu { - MENU_OPTION_CREATE_PHYSICAL_SKELETON + MENU_OPTION_CREATE_PHYSICAL_SKELETON, + MENU_OPTION_SAVE_DEFINITION }; struct BoneInfo { @@ -56,10 +58,15 @@ class SkeletonEditor : public Node { MenuButton *options; + EditorFileDialog *file_dialog; + void _on_click_option(int p_option); friend class SkeletonEditorPlugin; + void save_skeleton_definition(); + void _file_selected(const String &p_file); + protected: void _notification(int p_what); void _node_removed(Node *p_node); diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index ae79b4eebffd..a718251e3625 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -34,6 +34,7 @@ #include "core/project_settings.h" #include "scene/3d/physics_body.h" +#include "scene/resources/skeleton_definition.h" #include "scene/resources/surface_tool.h" void SkinReference::_skin_changed() { @@ -158,9 +159,14 @@ void Skeleton::_get_property_list(List *p_list) const { for (int i = 0; i < bones.size(); i++) { String prep = "bones/" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::STRING, prep + "name")); - p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "rest")); + // Only write out the bone properties if we dont have a skeleton defintiion, otherwise we will load them from + // there + if (skeleton_definition == nullptr) { + p_list->push_back(PropertyInfo(Variant::STRING, prep + "name")); + p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "rest")); + } + p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled")); p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "pose", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); p_list->push_back(PropertyInfo(Variant::ARRAY, prep + "bound_children")); @@ -354,7 +360,7 @@ Transform Skeleton::get_bone_global_pose(int p_bone) const { // skeleton creation api void Skeleton::add_bone(const String &p_name) { - + ERR_FAIL_COND(skeleton_definition != nullptr); ERR_FAIL_COND(p_name == "" || p_name.find(":") != -1 || p_name.find("/") != -1); for (int i = 0; i < bones.size(); i++) { @@ -405,7 +411,7 @@ int Skeleton::get_bone_count() const { } void Skeleton::set_bone_parent(int p_bone, int p_parent) { - + ERR_FAIL_COND(skeleton_definition != nullptr); ERR_FAIL_INDEX(p_bone, bones.size()); ERR_FAIL_COND(p_parent != -1 && (p_parent < 0)); @@ -415,7 +421,7 @@ void Skeleton::set_bone_parent(int p_bone, int p_parent) { } void Skeleton::unparent_bone_and_rest(int p_bone) { - + ERR_FAIL_COND(skeleton_definition != nullptr); ERR_FAIL_INDEX(p_bone, bones.size()); _update_process_order(); @@ -452,7 +458,7 @@ int Skeleton::get_bone_parent(int p_bone) const { } void Skeleton::set_bone_rest(int p_bone, const Transform &p_rest) { - + ERR_FAIL_COND(skeleton_definition != nullptr); ERR_FAIL_INDEX(p_bone, bones.size()); bones.write[p_bone].rest = p_rest; @@ -514,6 +520,7 @@ void Skeleton::get_bound_child_nodes_to_bone(int p_bone, List *p_bound) } void Skeleton::clear_bones() { + ERR_FAIL_COND(skeleton_definition != nullptr); bones.clear(); process_order_dirty = true; @@ -571,6 +578,7 @@ int Skeleton::get_process_order(int p_idx) { } void Skeleton::localize_rests() { + ERR_FAIL_COND(skeleton_definition != nullptr); _update_process_order(); @@ -796,6 +804,84 @@ Ref Skeleton::register_skin(const Ref &p_skin) { return skin_ref; } +void Skeleton::set_skeleton_definition(Ref p_definition) { + + struct BoneTemp { + String name; +#ifndef _3D_DISABLED + PhysicalBone *physical_bone; +#endif // _3D_DISABLED + List bound_nodes; + }; + + Vector bone_temps; + for (BoneId i = 0; i < bones.size(); ++i) { + BoneTemp temp; + temp.name = bones[i].name; + +#ifndef _3D_DISABLED + temp.physical_bone = bones[i].physical_bone; +#endif // _3D_DISABLED + + for (int j = 0; j < bones[i].nodes_bound.size(); ++j) { + Node *node = Object::cast_to(ObjectDB::get_instance(bones[i].nodes_bound[j])); + if (node) + temp.bound_nodes.push_back(node); + } + + bone_temps.push_back(temp); + } + + // Remove the skeleton definition first + skeleton_definition = Ref(); + if (p_definition == nullptr) + return; + + clear_bones(); + + const int bone_count = p_definition->get_bone_count(); + for (BoneId i = 0; i < bone_count; ++i) { + add_bone(p_definition->get_bone_name(i)); + set_bone_parent(i, p_definition->get_bone_parent(i)); + set_bone_rest(i, p_definition->get_bone_rest(i)); + } + + for (BoneId old_id = 0; old_id < bone_temps.size(); ++old_id) { + BoneTemp &bone = bone_temps.write[old_id]; + + BoneId new_id = find_bone(bone.name); + + if (new_id == -1) { + // We don't want to re-parent physical bones (if we cant match), just kill them off +#ifndef _3D_DISABLED + if (bone.physical_bone) { + bone.physical_bone->set_owner(nullptr); + bone.physical_bone = nullptr; + } +#endif // _3D_DISABLED + + // Attempt to re-parent any nodes to the root if we cant find anything + new_id = 0; + } + +#ifndef _3D_DISABLED + if (bone.physical_bone) { + bind_physical_bone_to_bone(new_id, bone.physical_bone); + } +#endif // _3D_DISABLED + + for (List::Element *el = bone.bound_nodes.front(); el != nullptr; el = el->next()) { + bind_child_node_to_bone(new_id, el->get()); + } + } + + skeleton_definition = p_definition; +} + +Ref Skeleton::get_skeleton_definition() const { + return skeleton_definition; +} + void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton::add_bone); @@ -843,6 +929,11 @@ void Skeleton::_bind_methods() { #endif // _3D_DISABLED + ClassDB::bind_method(D_METHOD("get_skeleton_definition"), &Skeleton::get_skeleton_definition); + ClassDB::bind_method(D_METHOD("set_skeleton_definition", "skeleton_definition"), &Skeleton::set_skeleton_definition); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skeleton_definition", PROPERTY_HINT_RESOURCE_TYPE), "set_skeleton_definition", "get_skeleton_definition"); + BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h index 824d9567fabe..127ae673c8f3 100644 --- a/scene/3d/skeleton.h +++ b/scene/3d/skeleton.h @@ -35,13 +35,17 @@ #include "scene/3d/spatial.h" #include "scene/resources/skin.h" -#ifndef _3D_DISABLED +#ifndef BONE_ID_DEF +#define BONE_ID_DEF typedef int BoneId; +#endif // BONE_ID_DEF +#ifndef _3D_DISABLED class PhysicalBone; #endif // _3D_DISABLED class Skeleton; +class SkeletonDefinition; class SkinReference : public Reference { GDCLASS(SkinReference, Reference) @@ -68,6 +72,7 @@ class Skeleton : public Spatial { private: friend class SkinReference; + friend class SkeletonDefinition; Set skin_bindings; @@ -122,6 +127,8 @@ class Skeleton : public Spatial { void _make_dirty(); bool dirty; + Ref skeleton_definition; + // bind helpers Array _get_bound_child_nodes_to_bone(int p_bone) const { @@ -196,6 +203,9 @@ class Skeleton : public Spatial { Ref register_skin(const Ref &p_skin); + void set_skeleton_definition(Ref p_definition); + Ref get_skeleton_definition() const; + #ifndef _3D_DISABLED // Physical bone API diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 314fc721fcfe..0c584161bd47 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -155,6 +155,7 @@ #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/resource_format_text.h" #include "scene/resources/segment_shape_2d.h" +#include "scene/resources/skeleton_definition.h" #include "scene/resources/sky.h" #include "scene/resources/sphere_shape.h" #include "scene/resources/surface_tool.h" @@ -368,6 +369,7 @@ void register_scene_types() { ClassDB::register_class(); ClassDB::register_virtual_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); diff --git a/scene/resources/skeleton_definition.cpp b/scene/resources/skeleton_definition.cpp new file mode 100644 index 000000000000..449f1200eca5 --- /dev/null +++ b/scene/resources/skeleton_definition.cpp @@ -0,0 +1,176 @@ +/*************************************************************************/ +/* skeleton_definition.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 "skeleton_definition.h" + +#include "scene/3d/skeleton.h" + +bool SkeletonDefinition::_get(const StringName &p_path, Variant &r_ret) const { + const String path = p_path; + + if (!path.begins_with("bones/")) + return false; + + const BoneId bone = path.get_slicec('/', 1).to_int(); + const String what = path.get_slicec('/', 2); + + if (what == "name") { + r_ret = get_bone_name(bone); + return true; + } else if (what == "parent") { + r_ret = get_bone_parent(bone); + return true; + } else if (what == "rest") { + r_ret = get_bone_rest(bone); + return true; + } + + return false; +} + +bool SkeletonDefinition::_set(const StringName &p_path, const Variant &p_value) { + const String path = p_path; + + if (!path.begins_with("bones/")) + return false; + + const BoneId bone = path.get_slicec('/', 1).to_int(); + const String what = path.get_slicec('/', 2); + + if (what == "name") { + add_bone(p_value); + return true; + } else if (what == "parent") { + set_bone_parent(bone, p_value); + return true; + } else if (what == "rest") { + set_bone_rest(bone, p_value); + return true; + } + + return false; +} + +void SkeletonDefinition::_get_property_list(List *p_list) const { + for (BoneId i = 0; i < bones.size(); ++i) { + String prep = "bones/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::STRING, prep + "name")); + p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "rest")); + } +} + +void SkeletonDefinition::add_bone(const String &p_name) { + ERR_FAIL_COND(p_name == "" || p_name.find(":") != -1 || p_name.find("/") != -1); + + for (BoneId i = 0; i < bones.size(); ++i) { + ERR_FAIL_COND(bones[i].name == p_name); + } + + Bone b; + b.name = p_name; + bones.push_back(b); +} + +BoneId SkeletonDefinition::find_bone(const String &p_name) const { + for (BoneId i = 0; i < bones.size(); ++i) { + if (bones[i].name == p_name) + return i; + } + + return -1; +} + +String SkeletonDefinition::get_bone_name(const BoneId p_bone) const { + ERR_FAIL_INDEX_V(p_bone, bones.size(), ""); + + return bones[p_bone].name; +} + +bool SkeletonDefinition::is_bone_parent_of(const BoneId p_bone, const BoneId p_parent_bone) const { + BoneId parent_of_bone = get_bone_parent(p_bone); + + if (parent_of_bone == -1) + return false; + + if (parent_of_bone == p_parent_bone) + return true; + + return is_bone_parent_of(parent_of_bone, p_parent_bone); +} + +void SkeletonDefinition::set_bone_parent(const BoneId p_bone, const BoneId p_parent) { + ERR_FAIL_INDEX(p_bone, bones.size()); + ERR_FAIL_COND(p_parent != -1 && (p_parent < 0)); + + bones.write[p_bone].parent = p_parent; +} + +BoneId SkeletonDefinition::get_bone_parent(const BoneId p_bone) const { + ERR_FAIL_INDEX_V(p_bone, bones.size(), -1); + + return bones[p_bone].parent; +} + +int SkeletonDefinition::get_bone_count() const { + return bones.size(); +} + +void SkeletonDefinition::set_bone_rest(const BoneId p_bone, const Transform &p_rest) { + ERR_FAIL_INDEX(p_bone, bones.size()); + + bones.write[p_bone].rest = p_rest; +} + +Transform SkeletonDefinition::get_bone_rest(const BoneId p_bone) const { + ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform()); + + return bones[p_bone].rest; +} + +void SkeletonDefinition::clear_bones() { + bones.clear(); +} + +Ref SkeletonDefinition::create_from_skeleton(const Skeleton *skeleton) { + Ref def; + def.instance(); + + const Vector &bones = skeleton->bones; + + for (BoneId i = 0; i < bones.size(); ++i) { + def->add_bone(bones[i].name); + def->set_bone_parent(i, bones[i].parent); + def->set_bone_rest(i, bones[i].rest); + } + + return def; +} diff --git a/scene/resources/skeleton_definition.h b/scene/resources/skeleton_definition.h new file mode 100644 index 000000000000..e51931bed331 --- /dev/null +++ b/scene/resources/skeleton_definition.h @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* skeleton_definition.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 SKELETON_DEFINITION_H +#define SKELETON_DEFINITION_H + +#include "core/resource.h" + +/** + @author Marios Staikopoulos +*/ + +#ifndef BONE_ID_DEF +#define BONE_ID_DEF +typedef int BoneId; +#endif // BONE_ID_DEF + +class Skeleton; + +class SkeletonDefinition : public Resource { + GDCLASS(SkeletonDefinition, Resource); + RES_BASE_EXTENSION("skel"); + +public: +private: + struct Bone { + String name; + BoneId parent; + Transform rest; + + Bone() : + parent(-1) { + } + }; + + Vector bones; + +protected: + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List *p_list) const; + +public: + void add_bone(const String &p_name); + BoneId find_bone(const String &p_name) const; + String get_bone_name(const BoneId p_bone) const; + + bool is_bone_parent_of(const BoneId p_bone_id, const BoneId p_parent_bone_id) const; + + void set_bone_parent(const BoneId p_bone, const BoneId p_parent); + BoneId get_bone_parent(const BoneId p_bone) const; + + int get_bone_count() const; + + void set_bone_rest(const BoneId p_bone, const Transform &p_rest); + Transform get_bone_rest(const BoneId p_bone) const; + + void clear_bones(); + + static Ref create_from_skeleton(const Skeleton *skeleton); +}; + +#endif