diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 9510037dd029..091f39d9455c 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -3341,7 +3341,7 @@ void AnimationTrackEditor::update_keying() { return; keying = keying_enabled; - //_update_menu(); + emit_signal("keying_changed"); } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index fe8cf9a363e9..87a781201ea9 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1354,13 +1354,25 @@ void EditorPropertyVector3::_value_changed(double val, const String &p_name) { } void EditorPropertyVector3::update_property() { - Vector3 val = get_edited_object()->get(get_edited_property()); + update_using_vector(get_edited_object()->get(get_edited_property())); +} + +void EditorPropertyVector3::update_using_vector(Vector3 p_vector) { setting = true; - spin[0]->set_value(val.x); - spin[1]->set_value(val.y); - spin[2]->set_value(val.z); + spin[0]->set_value(p_vector.x); + spin[1]->set_value(p_vector.y); + spin[2]->set_value(p_vector.z); setting = false; } + +Vector3 EditorPropertyVector3::get_vector() { + Vector3 v3; + v3.x = spin[0]->get_value(); + v3.y = spin[1]->get_value(); + v3.z = spin[2]->get_value(); + return v3; +} + void EditorPropertyVector3::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { Color base = get_color("accent_color", "Editor"); @@ -1860,6 +1872,24 @@ void EditorPropertyTransform::update_property() { setting = false; } + +void EditorPropertyTransform::update_using_transform(Transform p_transform) { + setting = true; + spin[0]->set_value(p_transform.basis[0][0]); + spin[1]->set_value(p_transform.basis[1][0]); + spin[2]->set_value(p_transform.basis[2][0]); + spin[3]->set_value(p_transform.basis[0][1]); + spin[4]->set_value(p_transform.basis[1][1]); + spin[5]->set_value(p_transform.basis[2][1]); + spin[6]->set_value(p_transform.basis[0][2]); + spin[7]->set_value(p_transform.basis[1][2]); + spin[8]->set_value(p_transform.basis[2][2]); + spin[9]->set_value(p_transform.origin[0]); + spin[10]->set_value(p_transform.origin[1]); + spin[11]->set_value(p_transform.origin[2]); + setting = false; +} + void EditorPropertyTransform::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { Color base = get_color("accent_color", "Editor"); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 1853a6b6e1eb..8f882ef2650c 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -389,6 +389,8 @@ class EditorPropertyVector3 : public EditorProperty { public: virtual void update_property(); + virtual void update_using_vector(Vector3 p_vector); + Vector3 get_vector(); void setup(double p_min, double p_max, double p_step, bool p_no_slider); EditorPropertyVector3(); }; @@ -485,6 +487,7 @@ class EditorPropertyTransform : public EditorProperty { public: virtual void update_property(); + virtual void update_using_transform(Transform p_transform); void setup(double p_min, double p_max, double p_step, bool p_no_slider); EditorPropertyTransform(); }; diff --git a/editor/plugins/skeleton_editor_plugin.cpp b/editor/plugins/skeleton_editor_plugin.cpp index 8b5fe7d2c5bc..7a8a12293e2c 100644 --- a/editor/plugins/skeleton_editor_plugin.cpp +++ b/editor/plugins/skeleton_editor_plugin.cpp @@ -30,13 +30,299 @@ #include "skeleton_editor_plugin.h" +#include "core/io/resource_saver.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_properties.h" +#include "editor/editor_scale.h" +#include "editor/plugins/animation_player_editor_plugin.h" #include "scene/3d/collision_shape.h" +#include "scene/3d/mesh_instance.h" #include "scene/3d/physics_body.h" #include "scene/3d/physics_joint.h" #include "scene/resources/capsule_shape.h" #include "scene/resources/sphere_shape.h" #include "spatial_editor_plugin.h" +void BoneTransformEditor::create_editors() { + const Color section_color = get_color("prop_subsection", "Editor"); + + section = memnew(EditorInspectorSection); + section->setup("trf_properties", label, this, section_color, true); + add_child(section); + + key_button = memnew(Button); + key_button->set_text(TTR("Key Transform")); + key_button->set_visible(keyable); + key_button->set_icon(get_icon("Key", "EditorIcons")); + key_button->set_flat(true); + section->get_vbox()->add_child(key_button); + + enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled"))); + enabled_checkbox->set_flat(true); + enabled_checkbox->set_visible(toggle_enabled); + section->get_vbox()->add_child(enabled_checkbox); + + // Translation property + translation_property = memnew(EditorPropertyVector3()); + translation_property->setup(-10000, 10000, 0.001f, true); + translation_property->set_label("Translation"); + translation_property->set_use_folding(true); + translation_property->set_read_only(false); + translation_property->connect("property_changed", this, "_value_changed_vector3"); + section->get_vbox()->add_child(translation_property); + + // Rotation property + rotation_property = memnew(EditorPropertyVector3()); + rotation_property->setup(-10000, 10000, 0.001f, true); + rotation_property->set_label("Rotation Degrees"); + rotation_property->set_use_folding(true); + rotation_property->set_read_only(false); + rotation_property->connect("property_changed", this, "_value_changed_vector3"); + section->get_vbox()->add_child(rotation_property); + + // Scale property + scale_property = memnew(EditorPropertyVector3()); + scale_property->setup(-10000, 10000, 0.001f, true); + scale_property->set_label("Scale"); + scale_property->set_use_folding(true); + scale_property->set_read_only(false); + scale_property->connect("property_changed", this, "_value_changed_vector3"); + section->get_vbox()->add_child(scale_property); + + // Transform/Matrix section + transform_section = memnew(EditorInspectorSection); + transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true); + section->get_vbox()->add_child(transform_section); + + // Transform/Matrix property + transform_property = memnew(EditorPropertyTransform()); + transform_property->setup(-10000, 10000, 0.001f, true); + transform_property->set_label("Transform"); + transform_property->set_use_folding(true); + transform_property->set_read_only(false); + transform_property->connect("property_changed", this, "_value_changed_transform"); + transform_section->get_vbox()->add_child(transform_property); +} + +void BoneTransformEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + key_button->connect("pressed", this, "_key_button_pressed"); + enabled_checkbox->connect("toggled", this, "_checkbox_toggled"); + FALLTHROUGH; + } + case NOTIFICATION_SORT_CHILDREN: { + const Ref font = get_font("font", "Tree"); + + Point2 buffer; + buffer.x += get_constant("inspector_margin", "Editor"); + buffer.y += font->get_height(); + buffer.y += get_constant("vseparation", "Tree"); + + const float vector_height = translation_property->get_size().y; + const float transform_height = transform_property->get_size().y; + const float button_height = key_button->get_size().y; + + const float width = get_size().x - get_constant("inspector_margin", "Editor"); + Vector input_rects; + if (keyable && section->get_vbox()->is_visible()) { + input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height))); + } else { + input_rects.push_back(Rect2(0, 0, 0, 0)); + } + + if (section->get_vbox()->is_visible()) { + input_rects.push_back(Rect2(translation_property->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(rotation_property->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(scale_property->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(transform_property->get_position() + buffer, Size2(width, transform_height))); + } else { + const int32_t start = input_rects.size(); + const int32_t empty_input_rect_elements = 4; + const int32_t end = start + empty_input_rect_elements; + for (int i = start; i < end; ++i) { + input_rects.push_back(Rect2(0, 0, 0, 0)); + } + } + + for (int32_t i = 0; i < input_rects.size(); i++) { + background_rects[i] = input_rects[i]; + } + + update(); + break; + } + case NOTIFICATION_DRAW: { + const Color dark_color = get_color("dark_color_2", "Editor"); + + for (int i = 0; i < 5; ++i) { + draw_rect(background_rects[i], dark_color); + } + + break; + } + } +} + +void BoneTransformEditor::_value_changed(const double p_value) { + if (updating) + return; + + Transform tform = compute_transform_from_vector3s(); + _change_transform(tform); +} + +void BoneTransformEditor::_value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean) { + if (updating) + return; + Transform tform = compute_transform_from_vector3s(); + _change_transform(tform); +} + +Transform BoneTransformEditor::compute_transform_from_vector3s() const { + // Convert rotation from degrees to radians + Vector3 prop_rotation = rotation_property->get_vector(); + prop_rotation.x = Math::deg2rad(prop_rotation.x); + prop_rotation.y = Math::deg2rad(prop_rotation.y); + prop_rotation.z = Math::deg2rad(prop_rotation.z); + + return Transform( + Basis(prop_rotation, scale_property->get_vector()), + translation_property->get_vector()); +} + +void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform p_transform, const StringName p_edited_property_name, const bool p_boolean) { + if (updating) { + return; + } + _change_transform(p_transform); +} + +void BoneTransformEditor::_change_transform(Transform p_new_transform) { + if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") { + undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS); + undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int())); + undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), p_new_transform); + undo_redo->commit_action(); + } else if (property.get_slicec('/', 0) == "bones") { + undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + undo_redo->add_undo_property(skeleton, property, skeleton->get(property)); + undo_redo->add_do_property(skeleton, property, p_new_transform); + undo_redo->commit_action(); + } +} + +void BoneTransformEditor::update_enabled_checkbox() { + if (enabled_checkbox) { + const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; + const bool is_enabled = skeleton->get(path); + enabled_checkbox->set_pressed(is_enabled); + } +} + +void BoneTransformEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_value_changed", "value"), &BoneTransformEditor::_value_changed); + ClassDB::bind_method(D_METHOD("_value_changed_transform", "name", "transform", "property_name", "boolean"), &BoneTransformEditor::_value_changed_transform); + ClassDB::bind_method(D_METHOD("_value_changed_vector3", "name", "vector", "property_name", "boolean"), &BoneTransformEditor::_value_changed_vector3); + ClassDB::bind_method(D_METHOD("_key_button_pressed"), &BoneTransformEditor::_key_button_pressed); + ClassDB::bind_method(D_METHOD("_checkbox_toggled", "toggled"), &BoneTransformEditor::_checkbox_toggled); +} + +void BoneTransformEditor::_update_properties() { + if (updating) + return; + + if (skeleton == nullptr) + return; + + updating = true; + + Transform tform = skeleton->get(property); + _update_transform_properties(tform); +} + +void BoneTransformEditor::_update_custom_pose_properties() { + if (updating) + return; + + if (skeleton == nullptr) + return; + + updating = true; + + Transform tform = skeleton->get_bone_custom_pose(property.to_int()); + _update_transform_properties(tform); +} + +void BoneTransformEditor::_update_transform_properties(Transform tform) { + Basis rotation_basis = tform.get_basis(); + Vector3 rotation_radians = rotation_basis.get_rotation_euler(); + Vector3 rotation_degrees = Vector3(Math::rad2deg(rotation_radians.x), Math::rad2deg(rotation_radians.y), Math::rad2deg(rotation_radians.z)); + Vector3 translation = tform.get_origin(); + Vector3 scale = tform.basis.get_scale(); + + translation_property->update_using_vector(translation); + rotation_property->update_using_vector(rotation_degrees); + scale_property->update_using_vector(scale); + transform_property->update_using_transform(tform); + + update_enabled_checkbox(); + updating = false; +} + +BoneTransformEditor::BoneTransformEditor(Skeleton *p_skeleton) : + skeleton(p_skeleton), + key_button(nullptr), + enabled_checkbox(nullptr), + keyable(false), + toggle_enabled(false), + updating(false) { + undo_redo = EditorNode::get_undo_redo(); +} + +void BoneTransformEditor::set_target(const String &p_prop) { + property = p_prop; +} + +void BoneTransformEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + if (key_button) { + key_button->set_visible(p_keyable); + } +} + +void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { + toggle_enabled = p_enabled; + if (enabled_checkbox) { + enabled_checkbox->set_visible(p_enabled); + } +} + +void BoneTransformEditor::_key_button_pressed() { + if (skeleton == nullptr) + return; + + const BoneId bone_id = property.get_slicec('/', 1).to_int(); + const String name = skeleton->get_bone_name(bone_id); + + if (name.empty()) + return; + + // Need to normalize the basis before you key it + Transform tform = compute_transform_from_vector3s(); + tform.basis.orthonormalize(); + tform.orthonormalize(); + AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); +} + +void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) { + if (enabled_checkbox) { + const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; + skeleton->set(path, p_toggled); + } +} + void SkeletonEditor::_on_click_option(int p_option) { if (!skeleton) { return; @@ -45,12 +331,14 @@ void SkeletonEditor::_on_click_option(int p_option) { switch (p_option) { case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { create_physical_skeleton(); - } break; + break; + } } } void SkeletonEditor::create_physical_skeleton() { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ERR_FAIL_COND(!get_tree()); Node *owner = skeleton == get_tree()->get_edited_scene_root() ? skeleton : skeleton->get_owner(); const int bc = skeleton->get_bone_count(); @@ -63,22 +351,18 @@ void SkeletonEditor::create_physical_skeleton() { bones_infos.resize(bc); for (int bone_id = 0; bc > bone_id; ++bone_id) { - const int parent = skeleton->get_bone_parent(bone_id); if (parent < 0) { - bones_infos.write[bone_id].relative_rest = skeleton->get_bone_rest(bone_id); } else { - const int parent_parent = skeleton->get_bone_parent(parent); bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); /// create physical bone on parent if (!bones_infos[parent].physical_bone) { - bones_infos.write[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos); ur->create_action(TTR("Create physical bones")); @@ -93,7 +377,6 @@ void SkeletonEditor::create_physical_skeleton() { /// Create joint between parent of parent if (-1 != parent_parent) { - bones_infos[parent].physical_bone->set_joint_type(PhysicalBone::JOINT_TYPE_PIN); } } @@ -102,9 +385,10 @@ void SkeletonEditor::create_physical_skeleton() { } PhysicalBone *SkeletonEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector &bones_infos) { + const Transform child_rest = skeleton->get_bone_rest(bone_child_id); - real_t half_height(skeleton->get_bone_rest(bone_child_id).origin.length() * 0.5); - real_t radius(half_height * 0.2); + const real_t half_height(child_rest.origin.length() * 0.5); + const real_t radius(half_height * 0.2); CapsuleShape *bone_shape_capsule = memnew(CapsuleShape); bone_shape_capsule->set_height((half_height - radius) * 2); @@ -114,7 +398,8 @@ PhysicalBone *SkeletonEditor::create_physical_bone(int bone_id, int bone_child_i bone_shape->set_shape(bone_shape_capsule); Transform body_transform; - body_transform.origin = Vector3(0, 0, -half_height); + body_transform.set_look_at(Vector3(0, 0, 0), child_rest.origin, Vector3(0, 1, 0)); + body_transform.origin = body_transform.basis.xform(Vector3(0, 0, -half_height)); Transform joint_transform; joint_transform.origin = Vector3(0, 0, half_height); @@ -127,32 +412,164 @@ PhysicalBone *SkeletonEditor::create_physical_bone(int bone_id, int bone_child_i return physical_bone; } -void SkeletonEditor::edit(Skeleton *p_node) { +Variant SkeletonEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + TreeItem *selected = joint_tree->get_selected(); + + if (!selected) + return Variant(); - skeleton = p_node; + Ref icon = selected->get_icon(0); + + VBoxContainer *vb = memnew(VBoxContainer); + HBoxContainer *hb = memnew(HBoxContainer); + TextureRect *tf = memnew(TextureRect); + tf->set_texture(icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hb->add_child(tf); + Label *label = memnew(Label(selected->get_text(0))); + hb->add_child(label); + vb->add_child(hb); + hb->set_modulate(Color(1, 1, 1, 1)); + + set_drag_preview(vb); + Dictionary drag_data; + drag_data["type"] = "nodes"; + drag_data["node"] = selected; + + return drag_data; } -void SkeletonEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - get_tree()->connect("node_removed", this, "_node_removed"); +bool SkeletonEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + TreeItem *target = joint_tree->get_item_at_position(p_point); + if (!target) + return false; + + const String path = target->get_metadata(0); + if (!path.begins_with("bones/")) + return false; + + TreeItem *selected = Object::cast_to(Dictionary(p_data)["node"]); + if (target == selected) + return false; + + const String path2 = target->get_metadata(0); + if (!path2.begins_with("bones/")) + return false; + + return true; +} + +void SkeletonEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!can_drop_data_fw(p_point, p_data, p_from)) + return; + + TreeItem *target = joint_tree->get_item_at_position(p_point); + TreeItem *selected = Object::cast_to(Dictionary(p_data)["node"]); + + const BoneId target_boneidx = String(target->get_metadata(0)).get_slicec('/', 1).to_int(); + const BoneId selected_boneidx = String(selected->get_metadata(0)).get_slicec('/', 1).to_int(); + + move_skeleton_bone(skeleton->get_path(), selected_boneidx, target_boneidx); +} + +void SkeletonEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx) { + Node *node = get_node_or_null(p_skeleton_path); + Skeleton *skeleton = Object::cast_to(node); + ERR_FAIL_NULL(skeleton); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Parentage")); + // If the target is a child of ourselves, we move only *us* and not our children + if (skeleton->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { + const BoneId parent_idx = skeleton->get_bone_parent(p_selected_boneidx); + const int bone_count = skeleton->get_bone_count(); + for (BoneId i = 0; i < bone_count; ++i) { + if (skeleton->get_bone_parent(i) == p_selected_boneidx) { + ur->add_undo_method(skeleton, "set_bone_parent", i, skeleton->get_bone_parent(i)); + ur->add_do_method(skeleton, "set_bone_parent", i, parent_idx); + skeleton->set_bone_parent(i, parent_idx); + } + } } + ur->add_undo_method(skeleton, "set_bone_parent", p_selected_boneidx, skeleton->get_bone_parent(p_selected_boneidx)); + ur->add_do_method(skeleton, "set_bone_parent", p_selected_boneidx, p_target_boneidx); + skeleton->set_bone_parent(p_selected_boneidx, p_target_boneidx); + + update_joint_tree(); + ur->commit_action(); } -void SkeletonEditor::_node_removed(Node *p_node) { +void SkeletonEditor::_joint_tree_selection_changed() { + TreeItem *selected = joint_tree->get_selected(); + const String path = selected->get_metadata(0); - if (p_node == skeleton) { - skeleton = NULL; - options->hide(); + if (path.begins_with("bones/")) { + const int b_idx = path.get_slicec('/', 1).to_int(); + const String bone_path = "bones/" + itos(b_idx) + "/"; + + pose_editor->set_target(bone_path + "pose"); + rest_editor->set_target(bone_path + "rest"); + custom_pose_editor->set_target(bone_path + "custom_pose"); + + pose_editor->set_visible(true); + rest_editor->set_visible(true); + custom_pose_editor->set_visible(true); } } -void SkeletonEditor::_bind_methods() { - ClassDB::bind_method("_on_click_option", &SkeletonEditor::_on_click_option); - ClassDB::bind_method("_node_removed", &SkeletonEditor::_node_removed); +void SkeletonEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { +} + +void SkeletonEditor::_update_properties() { + if (rest_editor) + rest_editor->_update_properties(); + if (pose_editor) + pose_editor->_update_properties(); + if (custom_pose_editor) + custom_pose_editor->_update_custom_pose_properties(); } -SkeletonEditor::SkeletonEditor() { - skeleton = NULL; +void SkeletonEditor::update_joint_tree() { + joint_tree->clear(); + + if (skeleton == nullptr) + return; + + TreeItem *root = joint_tree->create_item(); + + Map items; + + items.insert(-1, root); + + const Vector &joint_porder = skeleton->get_bone_process_orders(); + + Ref bone_icon = get_icon("BoneAttachment", "EditorIcons"); + + for (int i = 0; i < joint_porder.size(); ++i) { + const int b_idx = joint_porder[i]; + + const int p_idx = skeleton->get_bone_parent(b_idx); + TreeItem *p_item = items.find(p_idx)->get(); + + TreeItem *joint_item = joint_tree->create_item(p_item); + items.insert(b_idx, joint_item); + + joint_item->set_text(0, skeleton->get_bone_name(b_idx)); + joint_item->set_icon(0, bone_icon); + joint_item->set_selectable(0, true); + joint_item->set_metadata(0, "bones/" + itos(b_idx)); + } +} + +void SkeletonEditor::update_editors() { +} + +void SkeletonEditor::create_editors() { + set_h_size_flags(SIZE_EXPAND_FILL); + add_constant_override("separation", 0); + + set_focus_mode(FOCUS_ALL); + + // Create Top Menu Bar options = memnew(MenuButton); SpatialEditor::get_singleton()->add_control_to_menu_panel(options); @@ -162,33 +579,119 @@ SkeletonEditor::SkeletonEditor() { options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); options->get_popup()->connect("id_pressed", this, "_on_click_option"); - options->hide(); + + const Color section_color = get_color("prop_subsection", "Editor"); + + EditorInspectorSection *bones_section = memnew(EditorInspectorSection); + bones_section->setup("bones", "Bones", skeleton, section_color, true); + add_child(bones_section); + bones_section->unfold(); + + ScrollContainer *s_con = memnew(ScrollContainer); + s_con->set_h_size_flags(SIZE_EXPAND_FILL); + s_con->set_custom_minimum_size(Size2(1, 350) * EDSCALE); + bones_section->get_vbox()->add_child(s_con); + + joint_tree = memnew(Tree); + joint_tree->set_columns(1); + joint_tree->set_focus_mode(Control::FocusMode::FOCUS_NONE); + joint_tree->set_select_mode(Tree::SELECT_SINGLE); + joint_tree->set_hide_root(true); + joint_tree->set_v_size_flags(SIZE_EXPAND_FILL); + joint_tree->set_h_size_flags(SIZE_EXPAND_FILL); + joint_tree->set_allow_rmb_select(true); + joint_tree->set_drag_forwarding(this); + s_con->add_child(joint_tree); + + pose_editor = memnew(BoneTransformEditor(skeleton)); + pose_editor->set_label(TTR("Bone Pose")); + pose_editor->set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying()); + pose_editor->set_toggle_enabled(true); + pose_editor->set_visible(false); + add_child(pose_editor); + + rest_editor = memnew(BoneTransformEditor(skeleton)); + rest_editor->set_label(TTR("Bone Rest")); + rest_editor->set_visible(false); + add_child(rest_editor); + + custom_pose_editor = memnew(BoneTransformEditor(skeleton)); + custom_pose_editor->set_label(TTR("Bone Custom Pose")); + custom_pose_editor->set_visible(false); + add_child(custom_pose_editor); } -SkeletonEditor::~SkeletonEditor() {} +void SkeletonEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + update_joint_tree(); + update_editors(); + + get_tree()->connect("node_removed", this, "_node_removed", Vector(), Object::CONNECT_ONESHOT); + joint_tree->connect("item_selected", this, "_joint_tree_selection_changed"); + joint_tree->connect("item_rmb_selected", this, "_joint_tree_rmb_select"); +#ifdef TOOLS_ENABLED + skeleton->connect("pose_updated", this, "_update_properties"); +#endif // TOOLS_ENABLED + + break; + } + } +} -void SkeletonEditorPlugin::edit(Object *p_object) { - skeleton_editor->edit(Object::cast_to(p_object)); +void SkeletonEditor::_node_removed(Node *p_node) { + if (skeleton && p_node == skeleton) { + skeleton = nullptr; + options->hide(); + } + + _update_properties(); } -bool SkeletonEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("Skeleton"); +void SkeletonEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_node_removed"), &SkeletonEditor::_node_removed); + ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &SkeletonEditor::_joint_tree_selection_changed); + ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &SkeletonEditor::_joint_tree_rmb_select); + ClassDB::bind_method(D_METHOD("_update_properties"), &SkeletonEditor::_update_properties); + ClassDB::bind_method(D_METHOD("_on_click_option"), &SkeletonEditor::_on_click_option); + + ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &SkeletonEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &SkeletonEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &SkeletonEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &SkeletonEditor::move_skeleton_bone); } -void SkeletonEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - skeleton_editor->options->show(); - } else { +SkeletonEditor::SkeletonEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton *p_skeleton) : + editor(p_editor), + editor_plugin(e_plugin), + skeleton(p_skeleton) { +} - skeleton_editor->options->hide(); - skeleton_editor->edit(NULL); +SkeletonEditor::~SkeletonEditor() { + if (options) { + SpatialEditor::get_singleton()->remove_control_from_menu_panel(options); } } +bool EditorInspectorPluginSkeleton::can_handle(Object *p_object) { + return Object::cast_to(p_object) != nullptr; +} + +void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { + Skeleton *skeleton = Object::cast_to(p_object); + ERR_FAIL_COND(!skeleton); + + SkeletonEditor *skel_editor = memnew(SkeletonEditor(this, editor, skeleton)); + add_custom_control(skel_editor); +} + SkeletonEditorPlugin::SkeletonEditorPlugin(EditorNode *p_node) { editor = p_node; - skeleton_editor = memnew(SkeletonEditor); - editor->get_viewport()->add_child(skeleton_editor); -} -SkeletonEditorPlugin::~SkeletonEditorPlugin() {} + Ref skeleton_plugin; + skeleton_plugin.instance(); + skeleton_plugin->editor = editor; + + EditorInspector::add_inspector_plugin(skeleton_plugin); +} diff --git a/editor/plugins/skeleton_editor_plugin.h b/editor/plugins/skeleton_editor_plugin.h index 1dce6d12ed17..e4b91c771abb 100644 --- a/editor/plugins/skeleton_editor_plugin.h +++ b/editor/plugins/skeleton_editor_plugin.h @@ -35,62 +35,175 @@ #include "editor/editor_plugin.h" #include "scene/3d/skeleton.h" -class PhysicalBone; +class EditorInspectorPluginSkeleton; class Joint; +class PhysicalBone; +class SkeletonEditorPlugin; +class Button; +class CheckBox; +class EditorPropertyTransform; +class EditorPropertyVector3; + +class BoneTransformEditor : public VBoxContainer { + GDCLASS(BoneTransformEditor, VBoxContainer); + + EditorInspectorSection *section; + + EditorPropertyVector3 *translation_property; + EditorPropertyVector3 *rotation_property; + EditorPropertyVector3 *scale_property; + EditorInspectorSection *transform_section; + EditorPropertyTransform *transform_property; + + Rect2 background_rects[5]; + + Skeleton *skeleton; + String property; -class SkeletonEditor : public Node { - GDCLASS(SkeletonEditor, Node); + UndoRedo *undo_redo; + + Button *key_button; + CheckBox *enabled_checkbox; + + bool keyable; + bool toggle_enabled; + bool updating; + + String label; + + void create_editors(); + + // Called when one of the EditorSpinSliders are changed. + void _value_changed(const double p_value); + // Called when the one of the EditorPropertyVector3 are updated. + void _value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean); + // Called when the transform_property is updated. + void _value_changed_transform(const String p_property_name, const Transform p_transform, const StringName p_edited_property_name, const bool p_boolean); + // Changes the transform to the given transform and updates the UI accordingly. + void _change_transform(Transform p_new_transform); + // Creates a Transform using the EditorPropertyVector3 properties. + Transform compute_transform_from_vector3s() const; + + void update_enabled_checkbox(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + BoneTransformEditor(Skeleton *p_skeleton); + + // Which transform target to modify + void set_target(const String &p_prop); + void set_label(const String &p_label) { label = p_label; } + + void _update_properties(); + void _update_custom_pose_properties(); + void _update_transform_properties(Transform p_transform); + + // Can/cannot modify the spinner values for the Transform + void set_read_only(const bool p_read_only); + + // Transform can be keyed, whether or not to show the button + void set_keyable(const bool p_keyable); + + // Bone can be toggled enabled or disabled, whether or not to show the checkbox + void set_toggle_enabled(const bool p_enabled); + + // Key Transform Button pressed + void _key_button_pressed(); + + // Bone Enabled Checkbox toggled + void _checkbox_toggled(const bool p_toggled); +}; + +class SkeletonEditor : public VBoxContainer { + GDCLASS(SkeletonEditor, VBoxContainer); + + friend class SkeletonEditorPlugin; enum Menu { MENU_OPTION_CREATE_PHYSICAL_SKELETON }; struct BoneInfo { - PhysicalBone *physical_bone; + PhysicalBone *physical_bone = nullptr; Transform relative_rest; // Relative to skeleton node - BoneInfo() : - physical_bone(NULL) {} + BoneInfo() {} }; + EditorNode *editor; + EditorInspectorPluginSkeleton *editor_plugin; + Skeleton *skeleton; + Tree *joint_tree; + BoneTransformEditor *rest_editor; + BoneTransformEditor *pose_editor; + BoneTransformEditor *custom_pose_editor; + MenuButton *options; + EditorFileDialog *file_dialog; + + UndoRedo *undo_redo; void _on_click_option(int p_option); + void _file_selected(const String &p_file); - friend class SkeletonEditorPlugin; + EditorFileDialog *file_export_lib; + + void update_joint_tree(); + void update_editors(); + + void create_editors(); + + void create_physical_skeleton(); + PhysicalBone *create_physical_bone(int bone_id, int bone_child_id, const Vector &bones_infos); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); protected: void _notification(int p_what); void _node_removed(Node *p_node); static void _bind_methods(); - void create_physical_skeleton(); - PhysicalBone *create_physical_bone(int bone_id, int bone_child_id, const Vector &bones_infos); - public: - void edit(Skeleton *p_node); + void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx); + + Skeleton *get_skeleton() const { return skeleton; }; - SkeletonEditor(); + void _joint_tree_selection_changed(); + void _joint_tree_rmb_select(const Vector2 &p_pos); + + void _update_properties(); + + SkeletonEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton *skeleton); ~SkeletonEditor(); }; -class SkeletonEditorPlugin : public EditorPlugin { +class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginSkeleton, EditorInspectorPlugin); - GDCLASS(SkeletonEditorPlugin, EditorPlugin); + friend class SkeletonEditorPlugin; EditorNode *editor; - SkeletonEditor *skeleton_editor; public: - virtual String get_name() const { return "Skeleton"; } - virtual bool has_main_screen() const { return false; } - virtual void edit(Object *p_object); - virtual bool handles(Object *p_object) const; - virtual void make_visible(bool p_visible); + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); +}; +class SkeletonEditorPlugin : public EditorPlugin { + GDCLASS(SkeletonEditorPlugin, EditorPlugin); + + EditorNode *editor; + +public: SkeletonEditorPlugin(EditorNode *p_node); - ~SkeletonEditorPlugin(); + + virtual String get_name() const { return "Skeleton"; } }; #endif // SKELETON_EDITOR_PLUGIN_H diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index aafa52d16ae7..9779b6819faf 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -35,6 +35,7 @@ #include "core/project_settings.h" #include "scene/3d/physics_body.h" #include "scene/resources/surface_tool.h" +#include "scene/scene_string_names.h" void SkinReference::_skin_changed() { if (skeleton_node) { @@ -163,12 +164,12 @@ 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")); - 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")); + p_list->push_back(PropertyInfo(Variant::STRING, prep + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "pose", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::ARRAY, prep + "bound_children", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } } @@ -376,6 +377,10 @@ void Skeleton::_notification(int p_what) { dirty = false; emit_signal("skeleton_updated"); + +#ifdef TOOLS_ENABLED + emit_signal(SceneStringNames::get_singleton()->pose_updated); +#endif // TOOLS_ENABLED } break; } } @@ -623,8 +628,12 @@ int Skeleton::get_process_order(int p_idx) { return process_order[p_idx]; } -void Skeleton::localize_rests() { +Vector Skeleton::get_bone_process_orders() { + _update_process_order(); + return process_order; +} +void Skeleton::localize_rests() { _update_process_order(); for (int i = bones.size() - 1; i >= 0; i--) { @@ -845,12 +854,14 @@ Ref Skeleton::register_skin(const Ref &p_skin) { skin_bindings.insert(skin_ref.operator->()); skin->connect("changed", skin_ref.operator->(), "_skin_changed"); - _make_dirty(); + + _make_dirty(); //skin needs to be updated, so update skeleton + return skin_ref; } void Skeleton::_bind_methods() { - + ClassDB::bind_method(D_METHOD("get_bone_process_orders"), &Skeleton::get_bone_process_orders); ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton::add_bone); ClassDB::bind_method(D_METHOD("find_bone", "name"), &Skeleton::find_bone); ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton::get_bone_name); @@ -899,6 +910,10 @@ void Skeleton::_bind_methods() { ADD_SIGNAL(MethodInfo("skeleton_updated")); +#ifdef TOOLS_ENABLED + ADD_SIGNAL(MethodInfo("pose_updated")); +#endif // TOOLS_ENABLED + BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h index 473ba6cc5d32..6394057a751b 100644 --- a/scene/3d/skeleton.h +++ b/scene/3d/skeleton.h @@ -200,6 +200,7 @@ class Skeleton : public Spatial { void localize_rests(); // used for loaders and tools int get_process_order(int p_idx); + Vector get_bone_process_orders(); Ref register_skin(const Ref &p_skin); diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 6299c3a80134..7f2deb6875d0 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -63,6 +63,8 @@ SceneStringNames::SceneStringNames() { animation_changed = StaticCString::create("animation_changed"); animation_started = StaticCString::create("animation_started"); + pose_updated = StaticCString::create("pose_updated"); + mouse_entered = StaticCString::create("mouse_entered"); mouse_exited = StaticCString::create("mouse_exited"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index eeeaf22b01ab..173a9170b478 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -95,6 +95,8 @@ class SceneStringNames { StringName animation_changed; StringName animation_started; + StringName pose_updated; + StringName body_shape_entered; StringName body_entered; StringName body_shape_exited;