diff options
Diffstat (limited to 'editor')
46 files changed, 1601 insertions, 1116 deletions
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index 38db48a4d4..363d542fd5 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -126,28 +126,30 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event) { while (category) { TreeItem *input_item = category->get_first_child(); - // has_type this should be always true, unless the tree structure has been misconfigured. - bool has_type = input_item->get_parent()->has_meta("__type"); - int input_type = input_item->get_parent()->get_meta("__type"); - if (!has_type) { - return; - } + if (input_item != nullptr) { + // has_type this should be always true, unless the tree structure has been misconfigured. + bool has_type = input_item->get_parent()->has_meta("__type"); + int input_type = input_item->get_parent()->get_meta("__type"); + if (!has_type) { + return; + } - // If event type matches input types of this category. - if ((k.is_valid() && input_type == INPUT_KEY) || (joyb.is_valid() && input_type == INPUT_JOY_BUTTON) || (joym.is_valid() && input_type == INPUT_JOY_MOTION) || (mb.is_valid() && input_type == INPUT_MOUSE_BUTTON)) { - // Loop through all items of this category until one matches. - while (input_item) { - bool key_match = k.is_valid() && (Variant(k->get_keycode()) == input_item->get_meta("__keycode") || Variant(k->get_physical_keycode()) == input_item->get_meta("__keycode")); - bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index"); - bool joym_match = joym.is_valid() && Variant(joym->get_axis()) == input_item->get_meta("__axis") && joym->get_axis_value() == (float)input_item->get_meta("__value"); - bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index"); - if (key_match || joyb_match || joym_match || mb_match) { - category->set_collapsed(false); - input_item->select(0); - input_list_tree->ensure_cursor_is_visible(); - return; + // If event type matches input types of this category. + if ((k.is_valid() && input_type == INPUT_KEY) || (joyb.is_valid() && input_type == INPUT_JOY_BUTTON) || (joym.is_valid() && input_type == INPUT_JOY_MOTION) || (mb.is_valid() && input_type == INPUT_MOUSE_BUTTON)) { + // Loop through all items of this category until one matches. + while (input_item) { + bool key_match = k.is_valid() && (Variant(k->get_keycode()) == input_item->get_meta("__keycode") || Variant(k->get_physical_keycode()) == input_item->get_meta("__keycode")); + bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index"); + bool joym_match = joym.is_valid() && Variant(joym->get_axis()) == input_item->get_meta("__axis") && joym->get_axis_value() == (float)input_item->get_meta("__value"); + bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index"); + if (key_match || joyb_match || joym_match || mb_match) { + category->set_collapsed(false); + input_item->select(0); + input_list_tree->ensure_cursor_is_visible(); + return; + } + input_item = input_item->get_next(); } - input_item = input_item->get_next(); } } diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 14ca35a664..2ee7f0ffa5 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -165,20 +165,38 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d_old = animation->track_get_key_value(track, key); - Dictionary d_new = d_old.duplicate(); - d_new[p_name] = p_value; - setting = true; - undo_redo->create_action(TTR("Anim Change Transform")); - undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old); - undo_redo->add_do_method(this, "_update_obj", animation); - undo_redo->add_undo_method(this, "_update_obj", animation); - undo_redo->commit_action(); + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { + if (name == "position" || name == "rotation" || name == "scale") { + Variant old = animation->track_get_key_value(track, key); + setting = true; + String chan; + switch (animation->track_get_type(track)) { + case Animation::TYPE_POSITION_3D: + chan = "Position3D"; + break; + case Animation::TYPE_ROTATION_3D: + chan = "Rotation3D"; + break; + case Animation::TYPE_SCALE_3D: + chan = "Scale3D"; + break; + default: { + } + } + + undo_redo->create_action(vformat(TTR("Anim Change %s"), chan)); + undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + + setting = false; + return true; + } - setting = false; - return true; } break; case Animation::TYPE_VALUE: { if (name == "value") { @@ -412,12 +430,13 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d = animation->track_get_key_value(track, key); - ERR_FAIL_COND_V(!d.has(name), false); - r_ret = d[p_name]; - return true; - + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { + if (name == "position" || name == "rotation" || name == "scale") { + r_ret = animation->track_get_key_value(track, key); + return true; + } } break; case Animation::TYPE_VALUE: { if (name == "value") { @@ -523,11 +542,14 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - p_list->push_back(PropertyInfo(Variant::VECTOR3, "location")); - p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation")); + case Animation::TYPE_POSITION_3D: { + p_list->push_back(PropertyInfo(Variant::VECTOR3, "position")); + } break; + case Animation::TYPE_ROTATION_3D: { + p_list->push_back(PropertyInfo(Variant::VECTOR3, "rotation")); + } break; + case Animation::TYPE_SCALE_3D: { p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); - } break; case Animation::TYPE_VALUE: { Variant v = animation->track_get_key_value(track, key); @@ -779,17 +801,31 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d_old = animation->track_get_key_value(track, key); - Dictionary d_new = d_old.duplicate(); - d_new[p_name] = p_value; - + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { + Variant old = animation->track_get_key_value(track, key); if (!setting) { + String chan; + switch (animation->track_get_type(track)) { + case Animation::TYPE_POSITION_3D: + chan = "Position3D"; + break; + case Animation::TYPE_ROTATION_3D: + chan = "Rotation3D"; + break; + case Animation::TYPE_SCALE_3D: + chan = "Scale3D"; + break; + default: { + } + } + setting = true; - undo_redo->create_action(TTR("Anim Multi Change Transform")); + undo_redo->create_action(vformat(TTR("Anim Multi Change %s"), chan)); } - undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old); + undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old); update_obj = true; } break; case Animation::TYPE_VALUE: { @@ -1009,11 +1045,13 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d = animation->track_get_key_value(track, key); - ERR_FAIL_COND_V(!d.has(name), false); - r_ret = d[p_name]; - return true; + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { + if (name == "position" || name == "rotation" || name == "scale") { + r_ret = animation->track_get_key_value(track, key); + return true; + } } break; case Animation::TYPE_VALUE: { @@ -1159,9 +1197,13 @@ public: if (same_track_type) { switch (animation->track_get_type(first_track)) { - case Animation::TYPE_TRANSFORM3D: { - p_list->push_back(PropertyInfo(Variant::VECTOR3, "location")); - p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation")); + case Animation::TYPE_POSITION_3D: { + p_list->push_back(PropertyInfo(Variant::VECTOR3, "position")); + } break; + case Animation::TYPE_ROTATION_3D: { + p_list->push_back(PropertyInfo(Variant::QUATERNION, "scale")); + } break; + case Animation::TYPE_SCALE_3D: { p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); } break; case Animation::TYPE_VALUE: { @@ -1359,7 +1401,9 @@ void AnimationTimelineEdit::_notification(int p_what) { add_track->get_popup()->clear(); add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), TTR("Property Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXform"), SNAME("EditorIcons")), TTR("3D Transform Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")), TTR("3D Position Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")), TTR("3D Rotation Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")), TTR("3D Scale Track")); add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), TTR("Call Method Track")); add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), TTR("Bezier Curve Track")); add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), TTR("Audio Playback Track")); @@ -1828,9 +1872,11 @@ void AnimationTrackEdit::_notification(int p_what) { Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); - Ref<Texture2D> type_icons[6] = { + Ref<Texture2D> type_icons[8] = { get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyXform"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackPosition"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackRotation"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackScale"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), @@ -2021,7 +2067,7 @@ void AnimationTrackEdit::_notification(int p_what) { interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2; interp_mode_rect.size = icon->get_size(); - if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { draw_texture(icon, interp_mode_rect.position); } // Make it easier to click. @@ -2031,7 +2077,7 @@ void AnimationTrackEdit::_notification(int p_what) { ofs += icon->get_width() + hsep; interp_mode_rect.size.x += hsep; - if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); interp_mode_rect.size.x += down_icon->get_width(); } else { @@ -2054,7 +2100,7 @@ void AnimationTrackEdit::_notification(int p_what) { loop_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2; loop_mode_rect.size = icon->get_size(); - if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { draw_texture(icon, loop_mode_rect.position); } @@ -2064,7 +2110,7 @@ void AnimationTrackEdit::_notification(int p_what) { ofs += icon->get_width() + hsep; loop_mode_rect.size.x += hsep; - if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); loop_mode_rect.size.x += down_icon->get_width(); } else { @@ -2284,9 +2330,11 @@ void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animati track = p_track; update(); - Ref<Texture2D> type_icons[6] = { + Ref<Texture2D> type_icons[8] = { get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyXform"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), @@ -2456,17 +2504,17 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { if (key_idx != -1) { String text = TTR("Time (s): ") + rtos(animation->track_get_key_time(track, key_idx)) + "\n"; switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d = animation->track_get_key_value(track, key_idx); - if (d.has("location")) { - text += "Pos: " + String(d["location"]) + "\n"; - } - if (d.has("rotation")) { - text += "Rot: " + String(d["rotation"]) + "\n"; - } - if (d.has("scale")) { - text += "Scale: " + String(d["scale"]) + "\n"; - } + case Animation::TYPE_POSITION_3D: { + Vector3 t = animation->track_get_key_value(track, key_idx); + text += "Position: " + String(t) + "\n"; + } break; + case Animation::TYPE_ROTATION_3D: { + Quaternion t = animation->track_get_key_value(track, key_idx); + text += "Rotation: " + String(t) + "\n"; + } break; + case Animation::TYPE_SCALE_3D: { + Vector3 t = animation->track_get_key_value(track, key_idx); + text += "Scale: " + String(t) + "\n"; } break; case Animation::TYPE_VALUE: { const Variant &v = animation->track_get_key_value(track, key_idx); @@ -3323,7 +3371,11 @@ static bool track_type_is_resettable(Animation::TrackType p_type) { [[fallthrough]]; case Animation::TYPE_BEZIER: [[fallthrough]]; - case Animation::TYPE_TRANSFORM3D: + case Animation::TYPE_POSITION_3D: + [[fallthrough]]; + case Animation::TYPE_ROTATION_3D: + [[fallthrough]]; + case Animation::TYPE_SCALE_3D: return true; default: return false; @@ -3483,33 +3535,50 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_ NodePath np = path; - int track_idx = -1; + int position_idx = -1; + int rotation_idx = -1; + int scale_idx = -1; for (int i = 0; i < animation->get_track_count(); i++) { - if (animation->track_get_type(i) != Animation::TYPE_TRANSFORM3D) { - continue; - } if (animation->track_get_path(i) != np) { continue; } - track_idx = i; - break; + if (animation->track_get_type(i) == Animation::TYPE_POSITION_3D) { + position_idx = i; + } + if (animation->track_get_type(i) == Animation::TYPE_ROTATION_3D) { + rotation_idx = i; + } + if (animation->track_get_type(i) == Animation::TYPE_SCALE_3D) { + scale_idx = i; + } } InsertData id; - Dictionary val; - id.path = np; - id.track_idx = track_idx; - id.value = p_xform; - id.type = Animation::TYPE_TRANSFORM3D; // TRANSLATORS: This describes the target of new animation track, will be inserted into another string. id.query = vformat(TTR("node '%s'"), p_node->get_name()); id.advance = false; - // Dialog insert. - _query_insert(id); + { + id.track_idx = position_idx; + id.value = p_xform.origin; + id.type = Animation::TYPE_POSITION_3D; + _query_insert(id); + } + { + id.track_idx = rotation_idx; + id.value = p_xform.basis.get_rotation_quaternion(); + id.type = Animation::TYPE_ROTATION_3D; + _query_insert(id); + } + { + id.track_idx = scale_idx; + id.value = p_xform.basis.get_scale(); + id.type = Animation::TYPE_SCALE_3D; + _query_insert(id); + } } bool AnimationTrackEditor::has_transform_track(Node3D *p_node, const String &p_sub) { @@ -3530,7 +3599,7 @@ bool AnimationTrackEditor::has_transform_track(Node3D *p_node, const String &p_s } int track_id = animation->find_track(path); if (track_id >= 0) { - if (animation->track_get_type(track_id) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track_id) == Animation::TYPE_POSITION_3D || animation->track_get_type(track_id) == Animation::TYPE_ROTATION_3D || animation->track_get_type(track_id) == Animation::TYPE_SCALE_3D) { return true; } } @@ -3977,18 +4046,13 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD Variant value; switch (p_id.type) { + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: case Animation::TYPE_VALUE: { value = p_id.value; } break; - case Animation::TYPE_TRANSFORM3D: { - Transform3D tr = p_id.value; - Dictionary d; - d["location"] = tr.origin; - d["scale"] = tr.basis.get_scale(); - d["rotation"] = Quaternion(tr.basis); - value = d; - } break; case Animation::TYPE_BEZIER: { Array array; array.resize(5); @@ -4404,8 +4468,8 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { ERR_FAIL_COND(!node); NodePath path_to = root->get_path_to(node); - if (adding_track_type == Animation::TYPE_TRANSFORM3D && !node->is_class("Node3D")) { - EditorNode::get_singleton()->show_warning(TTR("Transform3D tracks only apply to 3D-based nodes.")); + if ((adding_track_type == Animation::TYPE_POSITION_3D || adding_track_type == Animation::TYPE_ROTATION_3D || adding_track_type == Animation::TYPE_SCALE_3D) && !node->is_class("Node3D")) { + EditorNode::get_singleton()->show_warning(TTR("Position/Rotation/Scale 3D tracks only apply to 3D-based nodes.")); return; } @@ -4415,7 +4479,9 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { prop_selector->set_type_filter(Vector<Variant::Type>()); prop_selector->select_property_from_instance(node); } break; - case Animation::TYPE_TRANSFORM3D: + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: case Animation::TYPE_METHOD: { undo_redo->create_action(TTR("Add Track")); undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type); @@ -4584,7 +4650,27 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { } switch (animation->track_get_type(p_track)) { - case Animation::TYPE_TRANSFORM3D: { + case Animation::TYPE_POSITION_3D: { + if (!root->has_node(animation->track_get_path(p_track))) { + EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key.")); + return; + } + Node3D *base = Object::cast_to<Node3D>(root->get_node(animation->track_get_path(p_track))); + + if (!base) { + EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key")); + return; + } + + Vector3 pos = base->get_position(); + + undo_redo->create_action(TTR("Add Position Key")); + undo_redo->add_do_method(animation.ptr(), "position_track_insert_key", p_track, p_ofs, pos); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_ROTATION_3D: { if (!root->has_node(animation->track_get_path(p_track))) { EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key.")); return; @@ -4596,14 +4682,30 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { return; } - Transform3D xf = base->get_transform(); + Quaternion rot = base->get_transform().basis.operator Quaternion(); + + undo_redo->create_action(TTR("Add Rotation Key")); + undo_redo->add_do_method(animation.ptr(), "rotation_track_insert_key", p_track, p_ofs, rot); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_SCALE_3D: { + if (!root->has_node(animation->track_get_path(p_track))) { + EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key.")); + return; + } + Node3D *base = Object::cast_to<Node3D>(root->get_node(animation->track_get_path(p_track))); + + if (!base) { + EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key")); + return; + } - Vector3 loc = xf.get_origin(); - Vector3 scale = xf.basis.get_scale_local(); - Quaternion rot = xf.basis; + Vector3 scale = base->get_scale(); - undo_redo->create_action(TTR("Add Transform Track Key")); - undo_redo->add_do_method(animation.ptr(), "transform_track_insert_key", p_track, p_ofs, loc, rot, scale); + undo_redo->create_action(TTR("Add Scale Key")); + undo_redo->add_do_method(animation.ptr(), "scale_track_insert_key", p_track, p_ofs, scale); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); undo_redo->commit_action(); @@ -5277,8 +5379,14 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } switch (animation->track_get_type(i)) { - case Animation::TYPE_TRANSFORM3D: - text += " (Transform)"; + case Animation::TYPE_POSITION_3D: + text += " (Position)"; + break; + case Animation::TYPE_ROTATION_3D: + text += " (Rotation)"; + break; + case Animation::TYPE_SCALE_3D: + text += " (Scale)"; break; case Animation::TYPE_METHOD: text += " (Methods)"; diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 576edef0c8..2555901557 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -35,7 +35,7 @@ #include "editor/editor_spin_slider.h" #include "editor/property_editor.h" #include "editor/property_selector.h" -#include "scene/animation/animation_cache.h" + #include "scene/gui/control.h" #include "scene/gui/file_dialog.h" #include "scene/gui/menu_button.h" diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index f17ad0d36c..f25f18b7e4 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -370,8 +370,8 @@ void EditorVisualProfiler::_update_frame(bool p_focus_selected) { float total_gpu = E->get_metadata(2); total_cpu += cpu_time; total_gpu += gpu_time; - E->set_metadata(1, cpu_time); - E->set_metadata(2, gpu_time); + E->set_metadata(1, total_cpu); + E->set_metadata(2, total_gpu); } category->set_icon(0, track_icon); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 953989aadd..767856f939 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1078,8 +1078,8 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const } for (int i = 0; i < p_dir->subdirs.size(); i++) { - if (updated_dir && !p_dir->subdirs[i]->verified) { - //this directory was removed, add action to remove it + if ((updated_dir && !p_dir->subdirs[i]->verified) || _should_skip_directory(p_dir->subdirs[i]->get_path())) { + //this directory was removed or ignored, add action to remove it ItemAction ia; ia.action = ItemAction::ACTION_DIR_REMOVE; ia.dir = p_dir->subdirs[i]; @@ -2163,7 +2163,7 @@ Error EditorFileSystem::_resource_import(const String &p_path) { } bool EditorFileSystem::_should_skip_directory(const String &p_path) { - if (p_path == ProjectSettings::get_singleton()->get_project_data_path()) { + if (p_path.begins_with(ProjectSettings::get_singleton()->get_project_data_path())) { return true; } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 5fd0a41788..2f92f60d5e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1776,19 +1776,25 @@ void EditorNode::restart_editor() { } void EditorNode::_save_all_scenes() { + bool all_saved = true; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { Node *scene = editor_data.get_edited_scene_root(i); - if (scene && scene->get_scene_file_path() != "" && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { - if (i != editor_data.get_edited_scene()) { - _save_scene(scene->get_scene_file_path(), i); + if (scene) { + if (scene->get_scene_file_path() != "" && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { + if (i != editor_data.get_edited_scene()) { + _save_scene(scene->get_scene_file_path(), i); + } else { + _save_scene_with_preview(scene->get_scene_file_path()); + } } else { - _save_scene_with_preview(scene->get_scene_file_path()); + all_saved = false; } - } else { - show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes")); } } + if (!all_saved) { + show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes")); + } _save_default_environment(); } @@ -3842,7 +3848,8 @@ void EditorNode::register_editor_types() { GDREGISTER_VIRTUAL_CLASS(EditorInterface); GDREGISTER_CLASS(EditorExportPlugin); GDREGISTER_CLASS(EditorResourceConversionPlugin); - GDREGISTER_CLASS(EditorSceneImporter); + GDREGISTER_CLASS(EditorSceneFormatImporter); + GDREGISTER_CLASS(EditorScenePostImportPlugin); GDREGISTER_CLASS(EditorInspector); GDREGISTER_CLASS(EditorInspectorPlugin); GDREGISTER_CLASS(EditorProperty); @@ -5929,7 +5936,7 @@ EditorNode::EditorNode() { ResourceFormatImporter::get_singleton()->add_importer(import_scene); { - Ref<EditorSceneImporterCollada> import_collada; + Ref<EditorSceneFormatImporterCollada> import_collada; import_collada.instantiate(); import_scene->add_importer(import_collada); @@ -5937,7 +5944,7 @@ EditorNode::EditorNode() { import_obj2.instantiate(); import_scene->add_importer(import_obj2); - Ref<EditorSceneImporterESCN> import_escn; + Ref<EditorSceneFormatImporterESCN> import_escn; import_escn.instantiate(); import_scene->add_importer(import_escn); } diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp index 71f13c0c2f..5b48cc2638 100644 --- a/editor/editor_paths.cpp +++ b/editor/editor_paths.cpp @@ -200,6 +200,20 @@ EditorPaths::EditorPaths() { paths_valid = false; } } + + // Check that the project data directory '.gdignore' file exists + String project_data_gdignore_file_path = project_data_dir.plus_file(".gdignore"); + if (!FileAccess::exists(project_data_gdignore_file_path)) { + // Add an empty .gdignore file to avoid scan. + FileAccessRef f = FileAccess::open(project_data_gdignore_file_path, FileAccess::WRITE); + if (f) { + f->store_line(""); + f->close(); + } else { + ERR_PRINT("Failed to create file " + project_data_gdignore_file_path); + } + } + Engine::get_singleton()->set_shader_cache_path(project_data_dir); // Editor metadata dir. diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index aee8322a97..61c01993ae 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -762,16 +762,23 @@ void EditorPlugin::remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_p EditorInspector::remove_inspector_plugin(p_plugin); } -void EditorPlugin::add_scene_import_plugin(const Ref<EditorSceneImporter> &p_importer) { +void EditorPlugin::add_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer) { ERR_FAIL_COND(!p_importer.is_valid()); ResourceImporterScene::get_singleton()->add_importer(p_importer); } -void EditorPlugin::remove_scene_import_plugin(const Ref<EditorSceneImporter> &p_importer) { +void EditorPlugin::remove_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer) { ERR_FAIL_COND(!p_importer.is_valid()); ResourceImporterScene::get_singleton()->remove_importer(p_importer); } +void EditorPlugin::add_scene_post_import_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin) { + ResourceImporterScene::get_singleton()->add_post_importer_plugin(p_plugin); +} +void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin) { + ResourceImporterScene::get_singleton()->remove_post_importer_plugin(p_plugin); +} + int find(const PackedStringArray &a, const String &v) { const String *r = a.ptr(); for (int j = 0; j < a.size(); ++j) { @@ -879,8 +886,10 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_translation_parser_plugin", "parser"), &EditorPlugin::remove_translation_parser_plugin); ClassDB::bind_method(D_METHOD("add_import_plugin", "importer"), &EditorPlugin::add_import_plugin); ClassDB::bind_method(D_METHOD("remove_import_plugin", "importer"), &EditorPlugin::remove_import_plugin); - ClassDB::bind_method(D_METHOD("add_scene_import_plugin", "scene_importer"), &EditorPlugin::add_scene_import_plugin); - ClassDB::bind_method(D_METHOD("remove_scene_import_plugin", "scene_importer"), &EditorPlugin::remove_scene_import_plugin); + ClassDB::bind_method(D_METHOD("add_scene_format_importer_plugin", "scene_format_importer"), &EditorPlugin::add_scene_format_importer_plugin); + ClassDB::bind_method(D_METHOD("remove_scene_format_importer_plugin", "scene_format_importer"), &EditorPlugin::remove_scene_format_importer_plugin); + ClassDB::bind_method(D_METHOD("add_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::add_scene_post_import_plugin); + ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin); ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin); ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin); ClassDB::bind_method(D_METHOD("add_spatial_gizmo_plugin", "plugin"), &EditorPlugin::add_spatial_gizmo_plugin); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 57830df327..278059f8c4 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -288,8 +288,11 @@ public: void add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); void remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); - void add_scene_import_plugin(const Ref<EditorSceneImporter> &p_importer); - void remove_scene_import_plugin(const Ref<EditorSceneImporter> &p_importer); + void add_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer); + void remove_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer); + + void add_scene_post_import_plugin(const Ref<EditorScenePostImportPlugin> &p_importer); + void remove_scene_post_import_plugin(const Ref<EditorScenePostImportPlugin> &p_importer); void add_autoload_singleton(const String &p_name, const String &p_path); void remove_autoload_singleton(const String &p_name); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 43d458c58e..223cc6650c 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -140,32 +140,37 @@ bool EditorSettings::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "shortcuts") { Array save_array; + const OrderedHashMap<String, List<Ref<InputEvent>>> &builtin_list = InputMap::get_singleton()->get_builtins(); for (const KeyValue<String, Ref<Shortcut>> &shortcut_definition : shortcuts) { Ref<Shortcut> sc = shortcut_definition.value; - if (builtin_action_overrides.has(shortcut_definition.key)) { + if (builtin_list.has(shortcut_definition.key)) { // This shortcut was auto-generated from built in actions: don't save. + // If the builtin is overriden, it will be saved in the "builtin_action_overrides" section below. continue; } - if (optimize_save) { - if (!sc->has_meta("original")) { - continue; //this came from settings but is not any longer used - } + Array shortcut_events = sc->get_events(); + + Dictionary dict; + dict["name"] = shortcut_definition.key; + dict["shortcuts"] = shortcut_events; + + if (!sc->has_meta("original")) { + // Getting the meta when it doesn't exist will return an empty array. If the 'shortcut_events' have been cleared, + // we still want save the shortcut in this case so that shortcuts that the user has customised are not reset, + // even if the 'original' has not been populated yet. This can happen when calling save() from the Project Manager. + save_array.push_back(dict); + continue; } Array original_events = sc->get_meta("original"); - Array shortcut_events = sc->get_events(); bool is_same = Shortcut::is_event_array_equal(original_events, shortcut_events); if (is_same) { continue; // Not changed from default; don't save. } - Dictionary dict; - dict["name"] = shortcut_definition.key; - dict["shortcuts"] = shortcut_events; - save_array.push_back(dict); } r_ret = save_array; @@ -1511,7 +1516,7 @@ void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, c // Directly override the existing shortcut. sc->set_events(events); - sc->set_meta("original", events); + sc->set_meta("original", events.duplicate(true)); } Ref<Shortcut> ED_SHORTCUT(const String &p_path, const String &p_name, Key p_keycode) { @@ -1545,21 +1550,21 @@ Ref<Shortcut> ED_SHORTCUT_ARRAY(const String &p_path, const String &p_name, cons sc.instantiate(); sc->set_name(p_name); sc->set_events(events); - sc->set_meta("original", events); + sc->set_meta("original", events.duplicate(true)); return sc; } Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_path); if (sc.is_valid()) { sc->set_name(p_name); //keep name (the ones that come from disk have no name) - sc->set_meta("original", events); //to compare against changes + sc->set_meta("original", events.duplicate(true)); //to compare against changes return sc; } sc.instantiate(); sc->set_name(p_name); sc->set_events(events); - sc->set_meta("original", events); //to compare against changes + sc->set_meta("original", events.duplicate(true)); //to compare against changes EditorSettings::get_singleton()->add_shortcut(p_path, sc); return sc; diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 283496c0f1..b61f6e12eb 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -235,7 +235,7 @@ void FindInFiles::_scan_dir(String path, PackedStringArray &out_folders) { // Ignore special dirs (such as .git and project data directory) String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); - if (file.begins_with(".") || file == project_data_dir_name) { + if (file.begins_with(".") || file.begins_with(project_data_dir_name)) { continue; } if (dir->current_is_hidden()) { diff --git a/editor/icons/KeyTrackPosition.svg b/editor/icons/KeyTrackPosition.svg new file mode 100644 index 0000000000..05c6e88841 --- /dev/null +++ b/editor/icons/KeyTrackPosition.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg12" + sodipodi:docname="KeyTrackPosition.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs16" /> + <sodipodi:namedview + id="namedview14" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="42.2" + inkscape:cx="12.78436" + inkscape:cy="6.1729858" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg12" /> + <ellipse + style="fill:none;fill-opacity:0.401212;stroke:none;stroke-width:4.7811;stroke-linejoin:round" + id="path921" + cx="-0.88986981" + cy="6.0959954" + rx="1.2495773" + ry="1.0867468" /> + <path + d="M 4.949661,0.60426977 A 0.6444116,0.6444116 0 0 0 4.504153,0.79178767 L 3.215459,2.0804819 4.12663,2.9916532 4.95977,2.1585124 5.792911,2.9916532 6.704083,2.0804819 5.415388,0.79178767 A 0.6444116,0.6444116 0 0 0 4.949744,0.60426977 Z M 1.926771,3.3691634 0.638077,4.6578577 a 0.6444116,0.6444116 0 0 0 0,0.9111713 L 1.926771,6.8577233 2.837942,5.946552 2.004801,5.1134111 2.837942,4.2802702 1.926771,3.3690989 Z m 6.065948,0 -0.911171,0.9111713 0.83314,0.8331409 -0.83314,0.8331408 0.911171,0.9111714 1.288694,-1.2886944 a 0.6444116,0.6444116 0 0 0 0,-0.9111713 L 7.992719,3.3692278 Z M 4.959777,3.8247361 A 1.2886943,1.2886943 0 0 0 3.671083,5.1134305 1.2886943,1.2886943 0 0 0 4.959777,6.4021248 1.2886943,1.2886943 0 0 0 6.248471,5.1134305 1.2886943,1.2886943 0 0 0 4.959777,3.8247361 Z m -0.83314,3.4105296 -0.911172,0.9111715 1.288694,1.288694 a 0.6444116,0.6444116 0 0 0 0.911171,0 L 6.704025,8.1464372 5.792853,7.2352657 4.959712,8.0684062 4.126572,7.2352657 Z" + fill="#e0e0e0" + fill-opacity="0.99608" + id="path1400" + style="fill:#ea7940;fill-opacity:1;stroke-width:0.644347" /> +</svg> diff --git a/editor/icons/KeyTrackRotation.svg b/editor/icons/KeyTrackRotation.svg new file mode 100644 index 0000000000..d05e381eb2 --- /dev/null +++ b/editor/icons/KeyTrackRotation.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg12" + sodipodi:docname="KeyTrackRotation.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs16" /> + <sodipodi:namedview + id="namedview14" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="42.2" + inkscape:cx="1.9075829" + inkscape:cy="5.8175355" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg12" /> + <ellipse + style="fill:none;fill-opacity:0.401212;stroke:none;stroke-width:4.7811;stroke-linejoin:round" + id="path921" + cx="-0.88986981" + cy="6.0959954" + rx="1.2495773" + ry="1.0867468" /> + <path + d="m 5.0711986,0.88214062 a 4.1086779,4.1086779 0 0 0 -0.178839,0.00115 4.1086779,4.1086779 0 0 0 -0.409265,0.033245 A 4.1086779,4.1086779 0 0 0 0.99001362,4.1883346 4.1086779,4.1086779 0 0 0 2.1467236,7.9244144 h -0.648877 v 1.1739077 h 2.347816 a 0.58701268,0.58701268 0 0 0 0.569756,-0.729119 l -0.586953,-2.3478164 -1.139514,0.2854534 0.165082,0.6580347 a 2.9347699,2.9347699 0 0 1 -0.769204,-1.9752178 2.9347699,2.9347699 0 0 1 2.93477,-2.93477 2.9347699,2.9347699 0 0 1 2.93477,2.93477 2.9347699,2.9347699 0 0 1 -0.860944,2.0738257 l 0.831127,0.8311266 A 4.1086779,4.1086779 0 0 0 8.7040866,3.1726236 4.1086779,4.1086779 0 0 0 5.0711336,0.88215292 Z m -0.05159,2.93359608 a 1.173908,1.173908 0 0 0 -1.173907,1.173908 1.173908,1.173908 0 0 0 1.173907,1.173908 1.173908,1.173908 0 0 0 1.173909,-1.173908 1.173908,1.173908 0 0 0 -1.173909,-1.173908 z" + fill="#e0e0e0" + fill-opacity="0.99608" + id="path1165" + style="fill:#ff2b88;fill-opacity:1;stroke-width:0.586954" /> +</svg> diff --git a/editor/icons/KeyTrackScale.svg b/editor/icons/KeyTrackScale.svg new file mode 100644 index 0000000000..9269ccbca2 --- /dev/null +++ b/editor/icons/KeyTrackScale.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg12" + sodipodi:docname="KeyTrackScale.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs16" /> + <sodipodi:namedview + id="namedview14" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="42.2" + inkscape:cx="1.9075829" + inkscape:cy="5.8175355" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg12" /> + <ellipse + style="fill:none;fill-opacity:0.401212;stroke:none;stroke-width:4.7811;stroke-linejoin:round" + id="path921" + cx="-0.88986981" + cy="6.0959954" + rx="1.2495773" + ry="1.0867468" /> + <path + d="M 5.5742494,0.94786723 A 0.58774585,0.58774585 0 0 0 4.9865036,1.535613 0.58774585,0.58774585 0 0 0 5.5742494,2.1233589 h 1.519852 L 6.334146,2.8833142 7.1652774,3.7144456 7.9252328,2.9544903 V 4.4743422 A 0.58774585,0.58774585 0 0 0 8.5129787,5.0620881 0.58774585,0.58774585 0 0 0 9.1007245,4.4743422 V 1.535613 A 0.58780462,0.58780462 0 0 0 8.5129787,0.94786723 Z M 4.9865036,3.8865964 A 1.1754917,1.1754917 0 0 0 3.8110119,5.0620881 1.1754917,1.1754917 0 0 0 4.9865036,6.2375798 1.1754917,1.1754917 0 0 0 6.1619953,5.0620881 1.1754917,1.1754917 0 0 0 4.9865036,3.8865964 Z M 1.4600285,5.0620881 A 0.58774585,0.58774585 0 0 0 0.8722826,5.6498339 V 8.5885638 A 0.58780462,0.58780462 0 0 0 1.4600285,9.1763092 H 4.3987577 A 0.58774585,0.58774585 0 0 0 4.9865036,8.5885638 0.58774585,0.58774585 0 0 0 4.3987577,8.0008173 H 2.8789057 L 3.6388611,7.2408619 2.8077297,6.4097305 2.0477743,7.1696859 V 5.6498339 A 0.58774585,0.58774585 0 0 0 1.4600285,5.0620881 Z" + fill="#e0e0e0" + fill-opacity="0.99608" + id="path7" + style="fill:#eac840;fill-opacity:1;stroke-width:0.587745" /> +</svg> diff --git a/editor/icons/KeyXPosition.svg b/editor/icons/KeyXPosition.svg new file mode 100644 index 0000000000..5816a241c9 --- /dev/null +++ b/editor/icons/KeyXPosition.svg @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg1678" + sodipodi:docname="KeyXPosition.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1682" /> + <sodipodi:namedview + id="namedview1680" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="84.4" + inkscape:cx="4.9940758" + inkscape:cy="5.0059242" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg1678" /> + <rect + fill="#ea7940" + height="6.1027" + ry=".76286" + transform="matrix(.70710678 -.70710678 .70710678 .70710678 0 -1042.4)" + width="6.1027" + x="-740.13947" + y="741.10779" + id="rect1676" /> +</svg> diff --git a/editor/icons/KeyXRotation.svg b/editor/icons/KeyXRotation.svg new file mode 100644 index 0000000000..6cd5a67ac0 --- /dev/null +++ b/editor/icons/KeyXRotation.svg @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg1678" + sodipodi:docname="KeyXRotation.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1682" /> + <sodipodi:namedview + id="namedview1680" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="84.4" + inkscape:cx="0.32582938" + inkscape:cy="5.0059242" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg1678" /> + <rect + fill="#ea7940" + height="6.1027002" + ry="0.76286" + transform="rotate(-45,-1258.2881,-521.2)" + width="6.1030002" + x="-740.13947" + y="741.10779" + id="rect1676" + style="fill:#ee3c94;fill-opacity:1" /> +</svg> diff --git a/editor/icons/KeyXScale.svg b/editor/icons/KeyXScale.svg new file mode 100644 index 0000000000..588fa5d2f3 --- /dev/null +++ b/editor/icons/KeyXScale.svg @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg1678" + sodipodi:docname="KeyXScale.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1682" /> + <sodipodi:namedview + id="namedview1680" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="84.4" + inkscape:cx="2.6658768" + inkscape:cy="5.0059242" + inkscape:window-width="1473" + inkscape:window-height="752" + inkscape:window-x="122" + inkscape:window-y="114" + inkscape:window-maximized="0" + inkscape:current-layer="svg1678" /> + <rect + fill="#ea7940" + height="6.1027002" + ry="0.76286" + transform="rotate(-45,-1258.2881,-521.2)" + width="6.1030002" + x="-740.13947" + y="741.10779" + id="rect1676" + style="fill:#dbbf4f;fill-opacity:1" /> +</svg> diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 4b01595028..4e343ce3ce 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -91,8 +91,8 @@ struct ColladaImport { Error _create_mesh_surfaces(bool p_optimize, Ref<ImporterMesh> &p_mesh, const Map<String, Collada::NodeGeometry::Material> &p_material_map, const Collada::MeshData &meshdata, const Transform3D &p_local_xform, const Vector<int> &bone_remap, const Collada::SkinControllerData *p_skin_controller, const Collada::MorphControllerData *p_morph_data, Vector<Ref<ImporterMesh>> p_morph_meshes = Vector<Ref<ImporterMesh>>(), bool p_use_compression = false, bool p_use_mesh_material = false); Error load(const String &p_path, int p_flags, bool p_force_make_tangents = false, bool p_use_compression = false); void _fix_param_animation_tracks(); - void create_animation(int p_clip, bool p_make_tracks_in_all_bones, bool p_import_value_tracks); - void create_animations(bool p_make_tracks_in_all_bones, bool p_import_value_tracks); + void create_animation(int p_clip, bool p_import_value_tracks); + void create_animations(bool p_import_value_tracks); Set<String> tracks_in_clips; Vector<String> missing_textures; @@ -120,6 +120,15 @@ Error ColladaImport::_populate_skeleton(Skeleton3D *p_skeleton, Collada::Node *p skeleton_bone_map[p_skeleton][joint->sid] = r_bone; + { + Transform3D xform = joint->compute_transform(collada); + collada.fix_transform(xform) * joint->post_transform; + + p_skeleton->set_bone_pose_position(r_bone, xform.origin); + p_skeleton->set_bone_pose_rotation(r_bone, xform.basis.get_rotation_quaternion()); + p_skeleton->set_bone_pose_scale(r_bone, xform.basis.get_scale()); + } + if (collada.state.bone_rest_map.has(joint->sid)) { p_skeleton->set_bone_rest(r_bone, collada.fix_transform(collada.state.bone_rest_map[joint->sid])); //should map this bone to something for animation? @@ -1384,7 +1393,7 @@ void ColladaImport::_fix_param_animation_tracks() { } } -void ColladaImport::create_animations(bool p_make_tracks_in_all_bones, bool p_import_value_tracks) { +void ColladaImport::create_animations(bool p_import_value_tracks) { _fix_param_animation_tracks(); for (int i = 0; i < collada.state.animation_clips.size(); i++) { for (int j = 0; j < collada.state.animation_clips[i].tracks.size(); j++) { @@ -1417,13 +1426,13 @@ void ColladaImport::create_animations(bool p_make_tracks_in_all_bones, bool p_im } } - create_animation(-1, p_make_tracks_in_all_bones, p_import_value_tracks); + create_animation(-1, p_import_value_tracks); for (int i = 0; i < collada.state.animation_clips.size(); i++) { - create_animation(i, p_make_tracks_in_all_bones, p_import_value_tracks); + create_animation(i, p_import_value_tracks); } } -void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones, bool p_import_value_tracks) { +void ColladaImport::create_animation(int p_clip, bool p_import_value_tracks) { Ref<Animation> animation = Ref<Animation>(memnew(Animation)); if (p_clip == -1) { @@ -1522,10 +1531,55 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones continue; } - animation->add_track(Animation::TYPE_TRANSFORM3D); - int track = animation->get_track_count() - 1; - animation->track_set_path(track, path); - animation->track_set_imported(track, true); //helps merging later + bool has_position = false; + bool has_rotation = false; + bool has_scale = false; + + for (int i = 0; cn->xform_list.size(); i++) { + switch (cn->xform_list[i].op) { + case Collada::Node::XForm::OP_ROTATE: { + has_rotation = true; + } break; + case Collada::Node::XForm::OP_SCALE: { + has_scale = true; + } break; + case Collada::Node::XForm::OP_TRANSLATE: { + has_position = true; + } break; + case Collada::Node::XForm::OP_MATRIX: { + has_position = true; + has_rotation = true; + has_scale = true; + } break; + case Collada::Node::XForm::OP_VISIBILITY: { + } break; + } + } + + int base_track = animation->get_track_count(); + int position_idx = -1; + if (has_position) { + position_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_POSITION_3D); + animation->track_set_path(position_idx, path); + animation->track_set_imported(position_idx, true); //helps merging later + } + + int rotation_idx = -1; + if (has_rotation) { + rotation_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_ROTATION_3D); + animation->track_set_path(rotation_idx, path); + animation->track_set_imported(rotation_idx, true); //helps merging later + } + + int scale_idx = -1; + if (has_scale) { + scale_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_SCALE_3D); + animation->track_set_path(scale_idx, path); + animation->track_set_imported(scale_idx, true); //helps merging later + } Vector<real_t> snapshots = base_snapshots; @@ -1594,22 +1648,21 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones Transform3D xform = cn->compute_transform(collada); xform = collada.fix_transform(xform) * cn->post_transform; - if (nm.bone >= 0) { - //make bone transform relative to rest (in case of skeleton) - Skeleton3D *sk = Object::cast_to<Skeleton3D>(nm.node); - if (sk) { - xform = sk->get_bone_rest(nm.bone).affine_inverse() * xform; - } else { - ERR_PRINT("Collada: Invalid skeleton"); - } - } - Vector3 s = xform.basis.get_scale(); bool singular_matrix = Math::is_zero_approx(s.x) || Math::is_zero_approx(s.y) || Math::is_zero_approx(s.z); - Quaternion q = singular_matrix ? Quaternion() : xform.basis.get_rotation_quaternion(); + Quaternion q = singular_matrix ? Quaternion() : + xform.basis.get_rotation_quaternion(); Vector3 l = xform.origin; - animation->transform_track_insert_key(track, snapshots[i], l, q, s); + if (position_idx >= 0) { + animation->position_track_insert_key(position_idx, snapshots[i], l); + } + if (rotation_idx >= 0) { + animation->rotation_track_insert_key(rotation_idx, snapshots[i], q); + } + if (scale_idx >= 0) { + animation->scale_track_insert_key(scale_idx, snapshots[i], s); + } } if (nm.bone >= 0) { @@ -1621,48 +1674,15 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones if (found_anim) { tracks_found = true; } else { - animation->remove_track(track); - } - } - - if (p_make_tracks_in_all_bones) { - //some bones may lack animation, but since we don't store pose as a property, we must add keyframes! - for (const KeyValue<String, bool> &E : bones_with_animation) { - if (E.value) { - continue; + if (position_idx >= 0) { + animation->remove_track(base_track); } - - NodeMap &nm = node_map[E.key]; - String path = scene->get_path_to(nm.node); - ERR_CONTINUE(nm.bone < 0); - Skeleton3D *sk = static_cast<Skeleton3D *>(nm.node); - String name = sk->get_bone_name(nm.bone); - path = path + ":" + name; - - Collada::Node *cn = collada.state.scene_map[E.key]; - if (cn->ignore_anim) { - WARN_PRINT("Collada: Ignoring animation on node: " + path); - continue; + if (rotation_idx >= 0) { + animation->remove_track(base_track); + } + if (scale_idx >= 0) { + animation->remove_track(base_track); } - - animation->add_track(Animation::TYPE_TRANSFORM3D); - int track = animation->get_track_count() - 1; - animation->track_set_path(track, path); - animation->track_set_imported(track, true); //helps merging later - - Transform3D xform = cn->compute_transform(collada); - xform = collada.fix_transform(xform) * cn->post_transform; - - xform = sk->get_bone_rest(nm.bone).affine_inverse() * xform; - - Vector3 s = xform.basis.get_scale(); - bool singular_matrix = Math::is_zero_approx(s.x) || Math::is_zero_approx(s.y) || Math::is_zero_approx(s.z); - Quaternion q = singular_matrix ? Quaternion() : xform.basis.get_rotation_quaternion(); - Vector3 l = xform.origin; - - animation->transform_track_insert_key(track, 0, l, q, s); - - tracks_found = true; } } @@ -1728,15 +1748,15 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones /*************************************** SCENE ***********************************/ /*********************************************************************************/ -uint32_t EditorSceneImporterCollada::get_import_flags() const { +uint32_t EditorSceneFormatImporterCollada::get_import_flags() const { return IMPORT_SCENE | IMPORT_ANIMATION; } -void EditorSceneImporterCollada::get_extensions(List<String> *r_extensions) const { +void EditorSceneFormatImporterCollada::get_extensions(List<String> *r_extensions) const { r_extensions->push_back("dae"); } -Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { +Node *EditorSceneFormatImporterCollada::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { if (r_err) { *r_err = OK; } @@ -1749,7 +1769,7 @@ Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_ state.use_mesh_builtin_materials = true; state.bake_fps = p_bake_fps; - Error err = state.load(p_path, flags, p_flags & EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS, false); + Error err = state.load(p_path, flags, p_flags & EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS, false); if (r_err) { *r_err = err; @@ -1773,7 +1793,7 @@ Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_ } if (p_flags & IMPORT_ANIMATION) { - state.create_animations(true, true); + state.create_animations(true); AnimationPlayer *ap = memnew(AnimationPlayer); for (int i = 0; i < state.animations.size(); i++) { String name; @@ -1792,15 +1812,15 @@ Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_ return state.scene; } -Ref<Animation> EditorSceneImporterCollada::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Ref<Animation> EditorSceneFormatImporterCollada::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { ColladaImport state; state.use_mesh_builtin_materials = false; - Error err = state.load(p_path, Collada::IMPORT_FLAG_ANIMATION, p_flags & EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS); + Error err = state.load(p_path, Collada::IMPORT_FLAG_ANIMATION, p_flags & EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS); ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load animation from file '" + p_path + "'."); - state.create_animations(true, true); + state.create_animations(true); if (state.scene) { memdelete(state.scene); } @@ -1813,5 +1833,5 @@ Ref<Animation> EditorSceneImporterCollada::import_animation(const String &p_path return anim; } -EditorSceneImporterCollada::EditorSceneImporterCollada() { +EditorSceneFormatImporterCollada::EditorSceneFormatImporterCollada() { } diff --git a/editor/import/editor_import_collada.h b/editor/import/editor_import_collada.h index bf45322765..055a6fe178 100644 --- a/editor/import/editor_import_collada.h +++ b/editor/import/editor_import_collada.h @@ -33,8 +33,8 @@ #include "editor/import/resource_importer_scene.h" -class EditorSceneImporterCollada : public EditorSceneImporter { - GDCLASS(EditorSceneImporterCollada, EditorSceneImporter); +class EditorSceneFormatImporterCollada : public EditorSceneFormatImporter { + GDCLASS(EditorSceneFormatImporterCollada, EditorSceneFormatImporter); public: virtual uint32_t get_import_flags() const override; @@ -42,7 +42,7 @@ public: virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps = nullptr, Error *r_err = nullptr) override; virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) override; - EditorSceneImporterCollada(); + EditorSceneFormatImporterCollada(); }; #endif diff --git a/editor/import/editor_importer_bake_reset.cpp b/editor/import/editor_importer_bake_reset.cpp deleted file mode 100644 index 451a07351c..0000000000 --- a/editor/import/editor_importer_bake_reset.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/*************************************************************************/ -/* editor_importer_bake_reset.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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 "editor/import/editor_importer_bake_reset.h" - -#include "core/error/error_list.h" -#include "core/error/error_macros.h" -#include "core/math/transform_3d.h" -#include "resource_importer_scene.h" -#include "scene/3d/importer_mesh_instance_3d.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/3d/node_3d.h" -#include "scene/3d/skeleton_3d.h" -#include "scene/animation/animation_player.h" - -// Given that an engineering team has made a reference character, one wants ten animators to create animations. -// Currently, a tech artist needs to combine the ten files into one exported gltf2 to import into Godot Engine. -// We bake the RESET animation and then set it to identity, -// so that rigs with corresponding RESET animation can have their animations transferred with ease. -// -// The original algorithm for the code was used to change skeleton bone rolls to be parent to child. -// -// Reference https://github.com/godotengine/godot-proposals/issues/2961 -void BakeReset::_bake_animation_pose(Node *scene, const String &p_bake_anim) { - Map<StringName, BakeResetRestBone> r_rest_bones; - Vector<Node3D *> r_meshes; - List<Node *> queue; - queue.push_back(scene); - while (!queue.is_empty()) { - List<Node *>::Element *E = queue.front(); - Node *node = E->get(); - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(node); - // Step 1: import scene with animations into the rest bones data structure. - _fetch_reset_animation(ap, r_rest_bones, p_bake_anim); - - int child_count = node->get_child_count(); - for (int i = 0; i < child_count; i++) { - queue.push_back(node->get_child(i)); - } - queue.pop_front(); - } - - queue.push_back(scene); - while (!queue.is_empty()) { - List<Node *>::Element *E = queue.front(); - Node *node = E->get(); - ImporterMeshInstance3D *editor_mesh_3d = scene->cast_to<ImporterMeshInstance3D>(node); - MeshInstance3D *mesh_3d = scene->cast_to<MeshInstance3D>(node); - if (scene->cast_to<Skeleton3D>(node)) { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); - - // Step 2: Bake the RESET animation from the RestBone to the skeleton. - _fix_skeleton(skeleton, r_rest_bones); - } - if (editor_mesh_3d) { - NodePath path = editor_mesh_3d->get_skeleton_path(); - if (!path.is_empty() && editor_mesh_3d->get_node_or_null(path) && Object::cast_to<Skeleton3D>(editor_mesh_3d->get_node_or_null(path))) { - r_meshes.push_back(editor_mesh_3d); - } - } else if (mesh_3d) { - NodePath path = mesh_3d->get_skeleton_path(); - if (!path.is_empty() && mesh_3d->get_node_or_null(path) && Object::cast_to<Skeleton3D>(mesh_3d->get_node_or_null(path))) { - r_meshes.push_back(mesh_3d); - } - } - int child_count = node->get_child_count(); - for (int i = 0; i < child_count; i++) { - queue.push_back(node->get_child(i)); - } - queue.pop_front(); - } - - queue.push_back(scene); - while (!queue.is_empty()) { - List<Node *>::Element *E = queue.front(); - Node *node = E->get(); - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(node); - if (ap) { - // Step 3: Key all RESET animation frames to identity. - _align_animations(ap, r_rest_bones); - } - - int child_count = node->get_child_count(); - for (int i = 0; i < child_count; i++) { - queue.push_back(node->get_child(i)); - } - queue.pop_front(); - } -} - -void BakeReset::_align_animations(AnimationPlayer *p_ap, const Map<StringName, BakeResetRestBone> &r_rest_bones) { - ERR_FAIL_NULL(p_ap); - List<StringName> anim_names; - p_ap->get_animation_list(&anim_names); - for (List<StringName>::Element *anim_i = anim_names.front(); anim_i; anim_i = anim_i->next()) { - Ref<Animation> a = p_ap->get_animation(anim_i->get()); - ERR_CONTINUE(a.is_null()); - for (const KeyValue<StringName, BakeResetRestBone> &rest_bone_i : r_rest_bones) { - int track = a->find_track(NodePath(rest_bone_i.key)); - if (track == -1) { - continue; - } - int new_track = a->add_track(Animation::TYPE_TRANSFORM3D); - NodePath new_path = NodePath(rest_bone_i.key); - const BakeResetRestBone rest_bone = rest_bone_i.value; - a->track_set_path(new_track, new_path); - for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { - Vector3 loc; - Quaternion rot; - Vector3 scale; - Error err = a->transform_track_get_key(track, key_i, &loc, &rot, &scale); - ERR_CONTINUE(err); - real_t time = a->track_get_key_time(track, key_i); - rot.normalize(); - loc = loc - rest_bone.loc; - rot = rest_bone.rest_delta.get_rotation_quaternion().inverse() * rot; - rot.normalize(); - scale = Vector3(1, 1, 1) - (rest_bone.rest_delta.get_scale() - scale); - // Apply the reverse of the rest changes to make the key be close to identity transform. - a->transform_track_insert_key(new_track, time, loc, rot, scale); - } - a->remove_track(track); - } - } -} - -void BakeReset::_fetch_reset_animation(AnimationPlayer *p_ap, Map<StringName, BakeResetRestBone> &r_rest_bones, const String &p_bake_anim) { - if (!p_ap) { - return; - } - List<StringName> anim_names; - p_ap->get_animation_list(&anim_names); - Node *root = p_ap->get_owner(); - ERR_FAIL_NULL(root); - if (!p_ap->has_animation(p_bake_anim)) { - return; - } - Ref<Animation> a = p_ap->get_animation(p_bake_anim); - if (a.is_null()) { - return; - } - for (int32_t track = 0; track < a->get_track_count(); track++) { - NodePath path = a->track_get_path(track); - String string_path = path; - Skeleton3D *skeleton = root->cast_to<Skeleton3D>(root->get_node(string_path.get_slice(":", 0))); - if (!skeleton) { - continue; - } - String bone_name = string_path.get_slice(":", 1); - for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { - Vector3 loc; - Quaternion rot; - Vector3 scale; - Error err = a->transform_track_get_key(track, key_i, &loc, &rot, &scale); - if (err != OK) { - ERR_PRINT_ONCE("Reset animation baker can't get key."); - continue; - } - rot.normalize(); - Basis rot_basis = Basis(rot, scale); - BakeResetRestBone rest_bone; - rest_bone.rest_delta = rot_basis; - rest_bone.loc = loc; - // Store the animation into the RestBone. - r_rest_bones[StringName(String(skeleton->get_owner()->get_path_to(skeleton)) + ":" + bone_name)] = rest_bone; - break; - } - } -} - -void BakeReset::_fix_skeleton(Skeleton3D *p_skeleton, Map<StringName, BakeReset::BakeResetRestBone> &r_rest_bones) { - int bone_count = p_skeleton->get_bone_count(); - - // First iterate through all the bones and update the RestBone. - for (int j = 0; j < bone_count; j++) { - StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(j); - BakeResetRestBone &rest_bone = r_rest_bones[final_path]; - rest_bone.rest_local = p_skeleton->get_bone_rest(j); - } - for (int i = 0; i < bone_count; i++) { - int parent_bone = p_skeleton->get_bone_parent(i); - String path = p_skeleton->get_owner()->get_path_to(p_skeleton); - StringName final_path = String(path) + String(":") + p_skeleton->get_bone_name(parent_bone); - if (parent_bone >= 0) { - r_rest_bones[path].children.push_back(i); - } - } - - // When we apply transform to a bone, we also have to move all of its children in the opposite direction. - for (int i = 0; i < bone_count; i++) { - StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(i); - r_rest_bones[final_path].rest_local = r_rest_bones[final_path].rest_local * Transform3D(r_rest_bones[final_path].rest_delta, r_rest_bones[final_path].loc); - // Iterate through the children and move in the opposite direction. - for (int j = 0; j < r_rest_bones[final_path].children.size(); j++) { - int child_index = r_rest_bones[final_path].children[j]; - StringName children_path = String(p_skeleton->get_name()) + String(":") + p_skeleton->get_bone_name(child_index); - r_rest_bones[children_path].rest_local = Transform3D(r_rest_bones[final_path].rest_delta, r_rest_bones[final_path].loc).affine_inverse() * r_rest_bones[children_path].rest_local; - } - } - - for (int i = 0; i < bone_count; i++) { - StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(i); - ERR_CONTINUE(!r_rest_bones.has(final_path)); - Transform3D rest_transform = r_rest_bones[final_path].rest_local; - p_skeleton->set_bone_rest(i, rest_transform); - } -} diff --git a/editor/import/editor_importer_bake_reset.h b/editor/import/editor_importer_bake_reset.h deleted file mode 100644 index e36ae86181..0000000000 --- a/editor/import/editor_importer_bake_reset.h +++ /dev/null @@ -1,54 +0,0 @@ -/*************************************************************************/ -/* editor_importer_bake_reset.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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 RESOURCE_IMPORTER_BAKE_RESET_H -#define RESOURCE_IMPORTER_BAKE_RESET_H - -#include "scene/main/node.h" - -class Skeleton3D; -class AnimationPlayer; -class BakeReset { - struct BakeResetRestBone { - Transform3D rest_local; - Basis rest_delta; - Vector3 loc; - Vector<int> children; - }; - -public: - void _bake_animation_pose(Node *scene, const String &p_bake_anim); - -private: - void _fix_skeleton(Skeleton3D *p_skeleton, Map<StringName, BakeReset::BakeResetRestBone> &r_rest_bones); - void _align_animations(AnimationPlayer *p_ap, const Map<StringName, BakeResetRestBone> &r_rest_bones); - void _fetch_reset_animation(AnimationPlayer *p_ap, Map<StringName, BakeResetRestBone> &r_rest_bones, const String &p_bake_anim); -}; -#endif diff --git a/editor/import/resource_importer_obj.h b/editor/import/resource_importer_obj.h index 1bb5ef33ce..d9f2f79903 100644 --- a/editor/import/resource_importer_obj.h +++ b/editor/import/resource_importer_obj.h @@ -33,8 +33,8 @@ #include "resource_importer_scene.h" -class EditorOBJImporter : public EditorSceneImporter { - GDCLASS(EditorOBJImporter, EditorSceneImporter); +class EditorOBJImporter : public EditorSceneFormatImporter { + GDCLASS(EditorOBJImporter, EditorSceneFormatImporter); public: virtual uint32_t get_import_flags() const override; diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 1e93113488..c393d7f5fc 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -32,7 +32,7 @@ #include "core/io/resource_saver.h" #include "editor/editor_node.h" -#include "editor/import/editor_importer_bake_reset.h" + #include "editor/import/scene_import_settings.h" #include "scene/3d/area_3d.h" #include "scene/3d/collision_shape_3d.h" @@ -52,7 +52,7 @@ #include "scene/resources/surface_tool.h" #include "scene/resources/world_boundary_shape_3d.h" -uint32_t EditorSceneImporter::get_import_flags() const { +uint32_t EditorSceneFormatImporter::get_import_flags() const { int ret; if (GDVIRTUAL_CALL(_get_import_flags, ret)) { return ret; @@ -61,7 +61,7 @@ uint32_t EditorSceneImporter::get_import_flags() const { ERR_FAIL_V(0); } -void EditorSceneImporter::get_extensions(List<String> *r_extensions) const { +void EditorSceneFormatImporter::get_extensions(List<String> *r_extensions) const { Vector<String> arr; if (GDVIRTUAL_CALL(_get_extensions, arr)) { for (int i = 0; i < arr.size(); i++) { @@ -73,7 +73,7 @@ void EditorSceneImporter::get_extensions(List<String> *r_extensions) const { ERR_FAIL(); } -Node *EditorSceneImporter::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { +Node *EditorSceneFormatImporter::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { Object *ret; if (GDVIRTUAL_CALL(_import_scene, p_path, p_flags, p_bake_fps, ret)) { return Object::cast_to<Node>(ret); @@ -82,7 +82,7 @@ Node *EditorSceneImporter::import_scene(const String &p_path, uint32_t p_flags, ERR_FAIL_V(nullptr); } -Ref<Animation> EditorSceneImporter::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Ref<Animation> EditorSceneFormatImporter::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { Ref<Animation> ret; if (GDVIRTUAL_CALL(_import_animation, p_path, p_flags, p_bake_fps, ret)) { return ret; @@ -94,17 +94,17 @@ Ref<Animation> EditorSceneImporter::import_animation(const String &p_path, uint3 //for documenters, these functions are useful when an importer calls an external conversion helper (like, fbx2gltf), //and you want to load the resulting file -Node *EditorSceneImporter::import_scene_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Node *EditorSceneFormatImporter::import_scene_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps) { return ResourceImporterScene::get_singleton()->import_scene_from_other_importer(this, p_path, p_flags, p_bake_fps); } -Ref<Animation> EditorSceneImporter::import_animation_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Ref<Animation> EditorSceneFormatImporter::import_animation_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps) { return ResourceImporterScene::get_singleton()->import_animation_from_other_importer(this, p_path, p_flags, p_bake_fps); } -void EditorSceneImporter::_bind_methods() { - ClassDB::bind_method(D_METHOD("import_scene_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneImporter::import_scene_from_other_importer); - ClassDB::bind_method(D_METHOD("import_animation_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneImporter::import_animation_from_other_importer); +void EditorSceneFormatImporter::_bind_methods() { + ClassDB::bind_method(D_METHOD("import_scene_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneFormatImporter::import_scene_from_other_importer); + ClassDB::bind_method(D_METHOD("import_animation_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneFormatImporter::import_animation_from_other_importer); GDVIRTUAL_BIND(_get_import_flags); GDVIRTUAL_BIND(_get_extensions); @@ -144,6 +144,105 @@ void EditorScenePostImport::init(const String &p_source_file) { EditorScenePostImport::EditorScenePostImport() { } +/////////////////////////////////////////////////////// + +Variant EditorScenePostImportPlugin::get_option_value(const StringName &p_name) const { + ERR_FAIL_COND_V_MSG(current_options == nullptr && current_options_dict == nullptr, Variant(), "get_option_value called from a function where option values are not available."); + ERR_FAIL_COND_V_MSG(current_options && !current_options->has(p_name), Variant(), "get_option_value called with unexisting option argument: " + String(p_name)); + ERR_FAIL_COND_V_MSG(current_options_dict && !current_options_dict->has(p_name), Variant(), "get_option_value called with unexisting option argument: " + String(p_name)); + if (current_options) { + (*current_options)[p_name]; + } + if (current_options_dict) { + (*current_options_dict)[p_name]; + } + return Variant(); +} +void EditorScenePostImportPlugin::add_import_option(const String &p_name, Variant p_default_value) { + ERR_FAIL_COND_MSG(current_option_list == nullptr, "add_import_option() can only be called from get_import_options()"); + add_import_option_advanced(p_default_value.get_type(), p_name, p_default_value); +} +void EditorScenePostImportPlugin::add_import_option_advanced(Variant::Type p_type, const String &p_name, Variant p_default_value, PropertyHint p_hint, const String &p_hint_string, int p_usage_flags) { + ERR_FAIL_COND_MSG(current_option_list == nullptr, "add_import_option_advanced() can only be called from get_import_options()"); + current_option_list->push_back(ResourceImporter::ImportOption(PropertyInfo(p_type, p_name, p_hint, p_hint_string, p_usage_flags), p_default_value)); +} + +void EditorScenePostImportPlugin::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) { + current_option_list = r_options; + GDVIRTUAL_CALL(_get_internal_import_options, p_category); + current_option_list = nullptr; +} +Variant EditorScenePostImportPlugin::get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const { + current_options = &p_options; + Variant ret; + GDVIRTUAL_CALL(_get_internal_option_visibility, p_category, p_option, ret); + current_options = nullptr; + return ret; +} +Variant EditorScenePostImportPlugin::get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const { + current_options = &p_options; + Variant ret; + GDVIRTUAL_CALL(_get_internal_option_update_view_required, p_category, p_option, ret); + current_options = nullptr; + return ret; +} + +void EditorScenePostImportPlugin::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, RES p_resource, const Dictionary &p_options) { + current_options_dict = &p_options; + GDVIRTUAL_CALL(_internal_process, p_category, p_base_scene, p_node, p_resource); + current_options_dict = nullptr; +} + +void EditorScenePostImportPlugin::get_import_options(List<ResourceImporter::ImportOption> *r_options) { + current_option_list = r_options; + GDVIRTUAL_CALL(_get_import_options); + current_option_list = nullptr; +} +Variant EditorScenePostImportPlugin::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { + current_options = &p_options; + Variant ret; + GDVIRTUAL_CALL(_get_option_visibility, p_option, ret); + current_options = nullptr; + return ret; +} + +void EditorScenePostImportPlugin::pre_process(Node *p_scene, const Map<StringName, Variant> &p_options) { + current_options = &p_options; + GDVIRTUAL_CALL(_pre_process, p_scene); + current_options = nullptr; +} +void EditorScenePostImportPlugin::post_process(Node *p_scene, const Map<StringName, Variant> &p_options) { + current_options = &p_options; + GDVIRTUAL_CALL(_post_process, p_scene); + current_options = nullptr; +} + +void EditorScenePostImportPlugin::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_option_value", "name"), &EditorScenePostImportPlugin::get_option_value); + + ClassDB::bind_method(D_METHOD("add_import_option", "name", "value"), &EditorScenePostImportPlugin::add_import_option); + ClassDB::bind_method(D_METHOD("add_import_option_advanced", "type", "name", "default_value", "hint", "hint_string", "usage_flags"), &EditorScenePostImportPlugin::add_import_option_advanced, DEFVAL(PROPERTY_HINT_NONE), DEFVAL(""), DEFVAL(PROPERTY_USAGE_DEFAULT)); + + GDVIRTUAL_BIND(_get_internal_import_options, "category"); + GDVIRTUAL_BIND(_get_internal_option_visibility, "category", "option"); + GDVIRTUAL_BIND(_get_internal_option_update_view_required, "category", "option"); + GDVIRTUAL_BIND(_internal_process, "category", "base_node", "node", "resource"); + GDVIRTUAL_BIND(_get_import_options); + GDVIRTUAL_BIND(_get_option_visibility, "option"); + GDVIRTUAL_BIND(_pre_process, "scene"); + GDVIRTUAL_BIND(_post_process, "scene"); + + BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_NODE); + BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE); + BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MESH); + BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MATERIAL); + BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_ANIMATION); + BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE); + BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MAX); +} + +///////////////////////////////////////////////////////// + String ResourceImporterScene::get_importer_name() const { return "scene"; } @@ -153,7 +252,7 @@ String ResourceImporterScene::get_visible_name() const { } void ResourceImporterScene::get_recognized_extensions(List<String> *p_extensions) const { - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { E->get()->get_extensions(p_extensions); } } @@ -181,6 +280,13 @@ bool ResourceImporterScene::get_option_visibility(const String &p_option, const return false; } + for (int i = 0; i < post_importer_plugins.size(); i++) { + Variant ret = post_importer_plugins.write[i]->get_option_visibility(p_option, p_options); + if (ret.get_type() == Variant::BOOL) { + return ret; + } + } + return true; } @@ -547,6 +653,26 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< return nullptr; } + { + ObjectID node_id = p_node->get_instance_id(); + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_NODE, p_root, p_node, RES(), node_settings); + if (ObjectDB::get_instance(node_id) == nullptr) { //may have been erased, so do not continue + break; + } + } + } + + if (Object::cast_to<ImporterMeshInstance3D>(p_node)) { + ObjectID node_id = p_node->get_instance_id(); + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, p_root, p_node, RES(), node_settings); + if (ObjectDB::get_instance(node_id) == nullptr) { //may have been erased, so do not continue + break; + } + } + } + if (Object::cast_to<ImporterMeshInstance3D>(p_node)) { ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); @@ -566,6 +692,11 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< if (mat_id != String() && p_material_data.has(mat_id)) { Dictionary matdata = p_material_data[mat_id]; + + for (int j = 0; j < post_importer_plugins.size(); j++) { + post_importer_plugins.write[j]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL, p_root, p_node, mat, matdata); + } + if (matdata.has("use_external/enabled") && bool(matdata["use_external/enabled"]) && matdata.has("use_external/path")) { String path = matdata["use_external/path"]; Ref<Material> external_mat = ResourceLoader::load(path); @@ -715,6 +846,10 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< } } + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, p_root, p_node, RES(), node_settings); + } + bool use_optimizer = node_settings["optimizer/enabled"]; float anim_optimizer_linerr = node_settings["optimizer/max_linear_error"]; float anim_optimizer_angerr = node_settings["optimizer/max_angular_error"]; @@ -779,6 +914,41 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< } } } + + AnimationImportTracks import_tracks_mode[TRACK_CHANNEL_MAX] = { + AnimationImportTracks(int(node_settings["import_tracks/position"])), + AnimationImportTracks(int(node_settings["import_tracks/rotation"])), + AnimationImportTracks(int(node_settings["import_tracks/scale"])) + }; + + if (anims.size() > 1 && (import_tracks_mode[0] != ANIMATION_IMPORT_TRACKS_IF_PRESENT || import_tracks_mode[1] != ANIMATION_IMPORT_TRACKS_IF_PRESENT || import_tracks_mode[2] != ANIMATION_IMPORT_TRACKS_IF_PRESENT)) { + _optimize_track_usage(ap, import_tracks_mode); + } + } + + if (post_importer_plugins.size()) { + List<StringName> anims; + ap->get_animation_list(&anims); + for (const StringName &name : anims) { + if (p_animation_data.has(name)) { + Ref<Animation> anim = ap->get_animation(name); + Dictionary anim_settings = p_animation_data[name]; + { + //fill with default values + List<ImportOption> iopts; + get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION, &iopts); + for (const ImportOption &F : iopts) { + if (!anim_settings.has(F.option.name)) { + anim_settings[F.option.name] = F.default_value; + } + } + } + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION, p_root, p_node, anim, node_settings); + } + } + } } } @@ -851,42 +1021,57 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_ new_anim->track_set_path(dtrack, default_anim->track_get_path(j)); if (kt > (from + 0.01) && k > 0) { - if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM3D) { - Quaternion q; + if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) { Vector3 p; + default_anim->position_track_interpolate(j, from, &p); + new_anim->position_track_insert_key(dtrack, 0, p); + } else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) { + Quaternion r; + default_anim->rotation_track_interpolate(j, from, &r); + new_anim->rotation_track_insert_key(dtrack, 0, r); + } else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) { Vector3 s; - default_anim->transform_track_interpolate(j, from, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, 0, p, q, s); - } - if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + default_anim->scale_track_interpolate(j, from, &s); + new_anim->scale_track_insert_key(dtrack, 0, s); + } else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { Variant var = default_anim->value_track_interpolate(j, from); new_anim->track_insert_key(dtrack, 0, var); } } } - if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM3D) { - Quaternion q; + if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) { Vector3 p; + default_anim->position_track_get_key(j, k, &p); + new_anim->position_track_insert_key(dtrack, kt - from, p); + } else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) { + Quaternion r; + default_anim->rotation_track_get_key(j, k, &r); + new_anim->rotation_track_insert_key(dtrack, kt - from, r); + } else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) { Vector3 s; - default_anim->transform_track_get_key(j, k, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, kt - from, p, q, s); - } - if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + default_anim->scale_track_get_key(j, k, &s); + new_anim->scale_track_insert_key(dtrack, kt - from, s); + } else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { Variant var = default_anim->track_get_key_value(j, k); new_anim->track_insert_key(dtrack, kt - from, var); } } if (dtrack != -1 && kt >= to) { - if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM3D) { - Quaternion q; + if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) { Vector3 p; + default_anim->position_track_interpolate(j, to, &p); + new_anim->position_track_insert_key(dtrack, to - from, p); + } else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) { + Quaternion r; + default_anim->rotation_track_interpolate(j, to, &r); + new_anim->rotation_track_insert_key(dtrack, to - from, r); + } else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) { Vector3 s; - default_anim->transform_track_interpolate(j, to, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, to - from, p, q, s); - } - if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + default_anim->scale_track_interpolate(j, to, &s); + new_anim->scale_track_insert_key(dtrack, to - from, s); + } else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { Variant var = default_anim->value_track_interpolate(j, to); new_anim->track_insert_key(dtrack, to - from, var); } @@ -897,16 +1082,25 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_ new_anim->add_track(default_anim->track_get_type(j)); dtrack = new_anim->get_track_count() - 1; new_anim->track_set_path(dtrack, default_anim->track_get_path(j)); - if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM3D) { - Quaternion q; + if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) { Vector3 p; + default_anim->position_track_interpolate(j, from, &p); + new_anim->position_track_insert_key(dtrack, 0, p); + default_anim->position_track_interpolate(j, to, &p); + new_anim->position_track_insert_key(dtrack, to - from, p); + } else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) { + Quaternion r; + default_anim->rotation_track_interpolate(j, from, &r); + new_anim->rotation_track_insert_key(dtrack, 0, r); + default_anim->rotation_track_interpolate(j, to, &r); + new_anim->rotation_track_insert_key(dtrack, to - from, r); + } else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) { Vector3 s; - default_anim->transform_track_interpolate(j, from, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, 0, p, q, s); - default_anim->transform_track_interpolate(j, to, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, to - from, p, q, s); - } - if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + default_anim->scale_track_interpolate(j, from, &s); + new_anim->scale_track_insert_key(dtrack, 0, s); + default_anim->scale_track_interpolate(j, to, &s); + new_anim->scale_track_insert_key(dtrack, to - from, s); + } else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { Variant var = default_anim->value_track_interpolate(j, from); new_anim->track_insert_key(dtrack, 0, var); Variant to_var = default_anim->value_track_interpolate(j, to); @@ -1000,6 +1194,9 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_linear_error"), 0.05)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angular_error"), 0.01)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angle"), 22)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/position", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/rotation", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/scale", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/amount", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); for (int i = 0; i < 256; i++) { @@ -1015,6 +1212,10 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p default: { } } + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->get_internal_import_options(EditorScenePostImportPlugin::InternalImportCategory(p_category), r_options); + } } bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const { @@ -1117,6 +1318,13 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor } } + for (int i = 0; i < post_importer_plugins.size(); i++) { + Variant ret = post_importer_plugins.write[i]->get_internal_option_visibility(EditorScenePostImportPlugin::InternalImportCategory(p_category), p_option, p_options); + if (ret.get_type() == Variant::BOOL) { + return ret; + } + } + return true; } @@ -1144,6 +1352,14 @@ bool ResourceImporterScene::get_internal_option_update_view_required(InternalImp default: { } } + + for (int i = 0; i < post_importer_plugins.size(); i++) { + Variant ret = post_importer_plugins.write[i]->get_internal_option_update_view_required(EditorScenePostImportPlugin::InternalImportCategory(p_category), p_option, p_options); + if (ret.get_type() == Variant::BOOL) { + return ret; + } + } + return false; } @@ -1171,11 +1387,14 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.1)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skins/use_named_skins"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import"), true)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/bake_reset_animation"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 15)); r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "import_script/path", PROPERTY_HINT_FILE, script_ext_hint), "")); r_options->push_back(ImportOption(PropertyInfo(Variant::DICTIONARY, "_subresources", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), Dictionary())); + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->get_import_options(r_options); + } } void ResourceImporterScene::_replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner) { @@ -1189,11 +1408,11 @@ void ResourceImporterScene::_replace_owner(Node *p_node, Node *p_scene, Node *p_ } } -Node *ResourceImporterScene::import_scene_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps) { - Ref<EditorSceneImporter> importer; +Node *ResourceImporterScene::import_scene_from_other_importer(EditorSceneFormatImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps) { + Ref<EditorSceneFormatImporter> importer; String ext = p_path.get_extension().to_lower(); - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { if (E->get().ptr() == p_exception) { continue; } @@ -1219,11 +1438,11 @@ Node *ResourceImporterScene::import_scene_from_other_importer(EditorSceneImporte return importer->import_scene(p_path, p_flags, p_bake_fps, &missing, &err); } -Ref<Animation> ResourceImporterScene::import_animation_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps) { - Ref<EditorSceneImporter> importer; +Ref<Animation> ResourceImporterScene::import_animation_from_other_importer(EditorSceneFormatImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps) { + Ref<EditorSceneFormatImporter> importer; String ext = p_path.get_extension().to_lower(); - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { if (E->get().ptr() == p_exception) { continue; } @@ -1320,6 +1539,10 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m save_to_file = ""; } } + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH, nullptr, src_mesh_node, src_mesh_node->get_mesh(), mesh_settings); + } } if (generate_lods) { @@ -1422,14 +1645,147 @@ void ResourceImporterScene::_add_shapes(Node *p_node, const Vector<Ref<Shape3D>> } } +void ResourceImporterScene::_optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions) { + List<StringName> anims; + p_player->get_animation_list(&anims); + Node *parent = p_player->get_parent(); + ERR_FAIL_COND(parent == nullptr); + OrderedHashMap<NodePath, uint32_t> used_tracks[TRACK_CHANNEL_MAX]; + bool tracks_to_add = false; + static const Animation::TrackType track_types[TRACK_CHANNEL_MAX] = { Animation::TYPE_POSITION_3D, Animation::TYPE_ROTATION_3D, Animation::TYPE_SCALE_3D }; + for (const StringName &I : anims) { + Ref<Animation> anim = p_player->get_animation(I); + for (int i = 0; i < anim->get_track_count(); i++) { + for (int j = 0; j < TRACK_CHANNEL_MAX; j++) { + if (anim->track_get_type(i) != track_types[j]) { + continue; + } + switch (p_track_actions[j]) { + case ANIMATION_IMPORT_TRACKS_IF_PRESENT: { + // Do Nothing. + } break; + case ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL: { + used_tracks[j].insert(anim->track_get_path(i), 0); + tracks_to_add = true; + } break; + case ANIMATION_IMPORT_TRACKS_NEVER: { + anim->remove_track(i); + i--; + } break; + } + } + } + } + + if (!tracks_to_add) { + return; + } + + uint32_t pass = 0; + for (const StringName &I : anims) { + Ref<Animation> anim = p_player->get_animation(I); + for (int j = 0; j < TRACK_CHANNEL_MAX; j++) { + if (p_track_actions[j] != ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL) { + continue; + } + + pass++; + + for (int i = 0; i < anim->get_track_count(); i++) { + if (anim->track_get_type(i) != track_types[j]) { + continue; + } + + NodePath path = anim->track_get_path(i); + + ERR_CONTINUE(!used_tracks[j].has(path)); // Should never happen. + + used_tracks[j][path] = pass; + } + + for (OrderedHashMap<NodePath, uint32_t>::Element J = used_tracks[j].front(); J; J = J.next()) { + if (J.get() == pass) { + continue; + } + + NodePath path = J.key(); + Node *n = parent->get_node(path); + Skeleton3D *skel = Object::cast_to<Skeleton3D>(n); + Node3D *n3d = Object::cast_to<Node3D>(n); + Vector3 loc; + Quaternion rot; + Vector3 scale; + if (skel && path.get_subname_count() > 0) { + StringName bone = path.get_subname(0); + int bone_idx = skel->find_bone(bone); + if (bone_idx == -1) { + continue; + } + skel->get_bone_pose(bone_idx); + loc = skel->get_bone_pose_position(bone_idx); + rot = skel->get_bone_pose_rotation(bone_idx); + scale = skel->get_bone_pose_scale(bone_idx); + } else if (n3d) { + loc = n3d->get_position(); + rot = n3d->get_transform().basis.get_rotation_quaternion(); + scale = n3d->get_scale(); + } else { + continue; + } + + // Ensure insertion keeps tracks together and ordered by type (loc/rot/scale) + int insert_at_pos = -1; + for (int k = 0; k < anim->get_track_count(); k++) { + NodePath tpath = anim->track_get_path(k); + + if (path == tpath) { + Animation::TrackType ttype = anim->track_get_type(k); + if (insert_at_pos == -1) { + // First insert, determine whether replacing or kicking back + if (track_types[j] < ttype) { + insert_at_pos = k; + break; // No point in continuing. + } else { + insert_at_pos = k + 1; + } + } else if (ttype < track_types[j]) { + // Kick back. + insert_at_pos = k + 1; + } + } else if (insert_at_pos >= 0) { + break; + } + } + int track_idx = anim->add_track(track_types[j], insert_at_pos); + + anim->track_set_path(track_idx, path); + anim->track_set_imported(track_idx, true); + switch (j) { + case TRACK_CHANNEL_POSITION: { + anim->position_track_insert_key(track_idx, 0, loc); + } break; + case TRACK_CHANNEL_ROTATION: { + anim->rotation_track_insert_key(track_idx, 0, rot); + } break; + case TRACK_CHANNEL_SCALE: { + anim->scale_track_insert_key(track_idx, 0, scale); + } break; + default: { + } + } + } + } + } +} + Node *ResourceImporterScene::pre_import(const String &p_source_file) { - Ref<EditorSceneImporter> importer; + Ref<EditorSceneFormatImporter> importer; String ext = p_source_file.get_extension().to_lower(); EditorProgress progress("pre-import", TTR("Pre-Import Scene"), 0); progress.step(TTR("Importing Scene..."), 0); - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { List<String> extensions; E->get()->get_extensions(&extensions); @@ -1448,7 +1804,7 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file) { ERR_FAIL_COND_V(!importer.is_valid(), nullptr); Error err = OK; - Node *scene = importer->import_scene(p_source_file, EditorSceneImporter::IMPORT_ANIMATION | EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS, 15, nullptr, &err); + Node *scene = importer->import_scene(p_source_file, EditorSceneFormatImporter::IMPORT_ANIMATION | EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS, 15, nullptr, &err); if (!scene || err != OK) { return nullptr; } @@ -1463,13 +1819,13 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file) { Error ResourceImporterScene::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { const String &src_path = p_source_file; - Ref<EditorSceneImporter> importer; + Ref<EditorSceneFormatImporter> importer; String ext = src_path.get_extension().to_lower(); EditorProgress progress("import", TTR("Import Scene"), 104); progress.step(TTR("Importing Scene..."), 0); - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { List<String> extensions; E->get()->get_extensions(&extensions); @@ -1492,16 +1848,16 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p int import_flags = 0; if (bool(p_options["animation/import"])) { - import_flags |= EditorSceneImporter::IMPORT_ANIMATION; + import_flags |= EditorSceneFormatImporter::IMPORT_ANIMATION; } if (bool(p_options["skins/use_named_skins"])) { - import_flags |= EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; + import_flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; } bool ensure_tangents = p_options["meshes/ensure_tangents"]; if (ensure_tangents) { - import_flags |= EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS; + import_flags |= EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS; } Error err = OK; @@ -1532,13 +1888,13 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> collision_map; _pre_fix_node(scene, scene, collision_map); - _post_fix_node(scene, scene, collision_map, scanned_meshes, node_data, material_data, animation_data, fps); - bool use_bake_reset_animation = p_options["animation/bake_reset_animation"]; - if (use_bake_reset_animation) { - BakeReset bake_reset; - bake_reset._bake_animation_pose(scene, "RESET"); + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->pre_process(scene, p_options); } + _post_fix_node(scene, scene, collision_map, scanned_meshes, node_data, material_data, animation_data, fps); + String root_type = p_options["nodes/root_type"]; root_type = root_type.split(" ")[0]; // full root_type is "ClassName (filename.gd)" for a script global class. @@ -1640,6 +1996,10 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p } } + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->post_process(scene, p_options); + } + progress.step(TTR("Saving..."), 104); Ref<PackedScene> packer = memnew(PackedScene); @@ -1671,15 +2031,15 @@ ResourceImporterScene::ResourceImporterScene() { /////////////////////////////////////// -uint32_t EditorSceneImporterESCN::get_import_flags() const { +uint32_t EditorSceneFormatImporterESCN::get_import_flags() const { return IMPORT_SCENE; } -void EditorSceneImporterESCN::get_extensions(List<String> *r_extensions) const { +void EditorSceneFormatImporterESCN::get_extensions(List<String> *r_extensions) const { r_extensions->push_back("escn"); } -Node *EditorSceneImporterESCN::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { +Node *EditorSceneFormatImporterESCN::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { Error error; Ref<PackedScene> ps = ResourceFormatLoaderText::singleton->load(p_path, p_path, &error); ERR_FAIL_COND_V_MSG(!ps.is_valid(), nullptr, "Cannot load scene as text resource from path '" + p_path + "'."); @@ -1690,6 +2050,6 @@ Node *EditorSceneImporterESCN::import_scene(const String &p_path, uint32_t p_fla return scene; } -Ref<Animation> EditorSceneImporterESCN::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Ref<Animation> EditorSceneFormatImporterESCN::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { ERR_FAIL_V(Ref<Animation>()); } diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index 2a67fa9aae..fab602b8ff 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -42,8 +42,8 @@ class Material; class AnimationPlayer; class ImporterMesh; -class EditorSceneImporter : public RefCounted { - GDCLASS(EditorSceneImporter, RefCounted); +class EditorSceneFormatImporter : public RefCounted { + GDCLASS(EditorSceneFormatImporter, RefCounted); protected: static void _bind_methods(); @@ -70,7 +70,7 @@ public: virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = nullptr); virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps); - EditorSceneImporter() {} + EditorSceneFormatImporter() {} }; class EditorScenePostImport : public RefCounted { @@ -90,10 +90,64 @@ public: EditorScenePostImport(); }; +class EditorScenePostImportPlugin : public RefCounted { + GDCLASS(EditorScenePostImportPlugin, RefCounted); + +public: + enum InternalImportCategory { + INTERNAL_IMPORT_CATEGORY_NODE, + INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, + INTERNAL_IMPORT_CATEGORY_MESH, + INTERNAL_IMPORT_CATEGORY_MATERIAL, + INTERNAL_IMPORT_CATEGORY_ANIMATION, + INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, + INTERNAL_IMPORT_CATEGORY_MAX + }; + +private: + mutable const Map<StringName, Variant> *current_options = nullptr; + mutable const Dictionary *current_options_dict = nullptr; + List<ResourceImporter::ImportOption> *current_option_list = nullptr; + InternalImportCategory current_category = INTERNAL_IMPORT_CATEGORY_MAX; + +protected: + GDVIRTUAL1(_get_internal_import_options, int) + GDVIRTUAL2RC(Variant, _get_internal_option_visibility, int, String) + GDVIRTUAL2RC(Variant, _get_internal_option_update_view_required, int, String) + GDVIRTUAL4(_internal_process, int, Node *, Node *, RES) + GDVIRTUAL0(_get_import_options) + GDVIRTUAL1RC(Variant, _get_option_visibility, String) + GDVIRTUAL1(_pre_process, Node *) + GDVIRTUAL1(_post_process, Node *) + + static void _bind_methods(); + +public: + Variant get_option_value(const StringName &p_name) const; + void add_import_option(const String &p_name, Variant p_default_value); + void add_import_option_advanced(Variant::Type p_type, const String &p_name, Variant p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT); + + virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options); + virtual Variant get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const; + virtual Variant get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const; + + virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, RES p_resource, const Dictionary &p_options); + + virtual void get_import_options(List<ResourceImporter::ImportOption> *r_options); + virtual Variant get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const; + + virtual void pre_process(Node *p_scene, const Map<StringName, Variant> &p_options); + virtual void post_process(Node *p_scene, const Map<StringName, Variant> &p_options); + + EditorScenePostImportPlugin() {} +}; + +VARIANT_ENUM_CAST(EditorScenePostImportPlugin::InternalImportCategory) + class ResourceImporterScene : public ResourceImporter { GDCLASS(ResourceImporterScene, ResourceImporter); - Set<Ref<EditorSceneImporter>> importers; + Set<Ref<EditorSceneFormatImporter>> importers; static ResourceImporterScene *singleton; @@ -144,13 +198,32 @@ class ResourceImporterScene : public ResourceImporter { void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches); void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes); + enum AnimationImportTracks { + ANIMATION_IMPORT_TRACKS_IF_PRESENT, + ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL, + ANIMATION_IMPORT_TRACKS_NEVER, + }; + enum TrackChannel { + TRACK_CHANNEL_POSITION, + TRACK_CHANNEL_ROTATION, + TRACK_CHANNEL_SCALE, + TRACK_CHANNEL_MAX + }; + + void _optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions); + + mutable Vector<Ref<EditorScenePostImportPlugin>> post_importer_plugins; + public: static ResourceImporterScene *get_singleton() { return singleton; } - const Set<Ref<EditorSceneImporter>> &get_importers() const { return importers; } + void add_post_importer_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin) { post_importer_plugins.push_back(p_plugin); } + void remove_post_importer_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin) { post_importer_plugins.erase(p_plugin); } + + const Set<Ref<EditorSceneFormatImporter>> &get_importers() const { return importers; } - void add_importer(Ref<EditorSceneImporter> p_importer) { importers.insert(p_importer); } - void remove_importer(Ref<EditorSceneImporter> p_importer) { importers.erase(p_importer); } + void add_importer(Ref<EditorSceneFormatImporter> p_importer) { importers.insert(p_importer); } + void remove_importer(Ref<EditorSceneFormatImporter> p_importer) { importers.erase(p_importer); } virtual String get_importer_name() const override; virtual String get_visible_name() const override; @@ -163,13 +236,13 @@ public: virtual String get_preset_name(int p_idx) const override; enum InternalImportCategory { - INTERNAL_IMPORT_CATEGORY_NODE, - INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, - INTERNAL_IMPORT_CATEGORY_MESH, - INTERNAL_IMPORT_CATEGORY_MATERIAL, - INTERNAL_IMPORT_CATEGORY_ANIMATION, - INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, - INTERNAL_IMPORT_CATEGORY_MAX + INTERNAL_IMPORT_CATEGORY_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_NODE, + INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, + INTERNAL_IMPORT_CATEGORY_MESH = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH, + INTERNAL_IMPORT_CATEGORY_MATERIAL = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL, + INTERNAL_IMPORT_CATEGORY_ANIMATION = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION, + INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, + INTERNAL_IMPORT_CATEGORY_MAX = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MAX }; void get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const; @@ -191,8 +264,8 @@ public: Node *pre_import(const String &p_source_file); virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; - Node *import_scene_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); - Ref<Animation> import_animation_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); + Node *import_scene_from_other_importer(EditorSceneFormatImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); + Ref<Animation> import_animation_from_other_importer(EditorSceneFormatImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); virtual bool has_advanced_options() const override; virtual void show_advanced_options(const String &p_path) override; @@ -208,8 +281,8 @@ public: static Transform3D get_collision_shapes_transform(const M &p_options); }; -class EditorSceneImporterESCN : public EditorSceneImporter { - GDCLASS(EditorSceneImporterESCN, EditorSceneImporter); +class EditorSceneFormatImporterESCN : public EditorSceneFormatImporter { + GDCLASS(EditorSceneFormatImporterESCN, EditorSceneFormatImporter); public: virtual uint32_t get_import_flags() const override; diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index 5b52554335..a6e1e1d094 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -130,6 +130,7 @@ void ImportDock::set_edit_path(const String &p_path) { _add_keep_import_option(importer_name); import->set_disabled(false); + _set_dirty(false); import_as->set_disabled(false); preset->set_disabled(false); @@ -280,6 +281,7 @@ void ImportDock::set_edit_multiple_paths(const Vector<String> &p_paths) { params->paths = p_paths; import->set_disabled(false); + _set_dirty(false); import_as->set_disabled(false); preset->set_disabled(false); @@ -533,6 +535,8 @@ void ImportDock::_reimport() { EditorFileSystem::get_singleton()->reimport_files(params->paths); EditorFileSystem::get_singleton()->emit_signal(SNAME("filesystem_changed")); //it changed, so force emitting the signal + + _set_dirty(false); } void ImportDock::_notification(int p_what) { @@ -548,6 +552,24 @@ void ImportDock::_notification(int p_what) { } } +void ImportDock::_property_edited(const StringName &p_prop) { + _set_dirty(true); +} + +void ImportDock::_set_dirty(bool p_dirty) { + if (p_dirty) { + // Add a dirty marker to notify the user that they should reimport the selected resource to see changes. + import->set_text(TTR("Reimport") + " (*)"); + import->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + import->set_tooltip(TTR("You have pending changes that haven't been applied yet. Click Reimport to apply changes made to the import options.\nSelecting another resource in the FileSystem dock without clicking Reimport first will discard changes made in the Import dock.")); + } else { + // Remove the dirty marker on the Reimport button. + import->set_text(TTR("Reimport")); + import->remove_theme_color_override("font_color"); + import->set_tooltip(""); + } +} + void ImportDock::_property_toggled(const StringName &p_prop, bool p_checked) { if (p_checked) { params->checked.insert(p_prop); @@ -588,6 +610,7 @@ ImportDock::ImportDock() { import_opts = memnew(EditorInspector); add_child(import_opts); import_opts->set_v_size_flags(SIZE_EXPAND_FILL); + import_opts->connect("property_edited", callable_mp(this, &ImportDock::_property_edited)); import_opts->connect("property_toggled", callable_mp(this, &ImportDock::_property_toggled)); hb = memnew(HBoxContainer); diff --git a/editor/import_dock.h b/editor/import_dock.h index 3c28bbcd89..150c44576d 100644 --- a/editor/import_dock.h +++ b/editor/import_dock.h @@ -68,7 +68,9 @@ class ImportDock : public VBoxContainer { void _update_preset_menu(); void _add_keep_import_option(const String &p_importer_name); + void _property_edited(const StringName &p_prop); void _property_toggled(const StringName &p_prop, bool p_checked); + void _set_dirty(bool p_dirty); void _reimport_attempt(); void _reimport_and_restart(); void _reimport(); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index ef872bcead..8935f715e6 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1174,7 +1174,6 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo if (!panning) { if (b->is_pressed() && (b->get_button_index() == MOUSE_BUTTON_MIDDLE || - b->get_button_index() == MOUSE_BUTTON_RIGHT || (b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_PAN) || (b->get_button_index() == MOUSE_BUTTON_LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { // Pan the viewport @@ -1232,8 +1231,9 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } } - if (is_pan_key) { + if (is_pan_key && pan_pressed != k->is_pressed()) { pan_pressed = k->is_pressed(); + _update_cursor(); } } @@ -2615,7 +2615,19 @@ void CanvasItemEditor::_update_cursor() { c = CURSOR_HSIZE; } - viewport->set_default_cursor_shape(c); + if (pan_pressed) { + c = CURSOR_DRAG; + } + + if (c != viewport->get_default_cursor_shape()) { + viewport->set_default_cursor_shape(c); + + // Force refresh cursor if it's over the viewport. + if (viewport->get_global_rect().has_point(get_global_mouse_position())) { + DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); + DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); + } + } } void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string, Side p_side) { @@ -4254,10 +4266,6 @@ void CanvasItemEditor::_button_tool_select(int p_index) { viewport->update(); _update_cursor(); - - // Request immediate refresh of cursor when using hot-keys to switch between tools - DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); - DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); } void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) { @@ -5384,7 +5392,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { pan_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_PAN)); pan_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTR("Pan Mode"), KEY_G)); pan_button->set_shortcut_context(this); - pan_button->set_tooltip(TTR("Pan Mode")); + pan_button->set_tooltip(TTR("You can also use Pan View shortcut (Space by default) to pan in any mode.")); ruler_button = memnew(Button); ruler_button->set_flat(true); diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index f1b25c0dde..4d2fc29fe0 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -832,7 +832,7 @@ void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin); ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear); ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden); - ClassDB::bind_method(D_METHOD("is_subgizmo_selected"), &EditorNode3DGizmo::is_subgizmo_selected); + ClassDB::bind_method(D_METHOD("is_subgizmo_selected", "id"), &EditorNode3DGizmo::is_subgizmo_selected); ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection); GDVIRTUAL_BIND(_redraw); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index ea6ef8ab84..8600ef3ae8 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -3863,7 +3863,7 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, } Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const { - const float MAX_DISTANCE = 10; + const float MAX_DISTANCE = 50.0; Vector3 world_ray = _get_ray(p_pos); Vector3 world_pos = _get_ray_pos(p_pos); @@ -6469,7 +6469,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { // We add a bit of margin to the from position to avoid it from snapping // when the spatial is already on a floor and there's another floor under // it - from = from + Vector3(0.0, 0.2, 0.0); + from = from + Vector3(0.0, 1, 0.0); Dictionary d; @@ -6485,7 +6485,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { Array keys = snap_data.keys(); // The maximum height an object can travel to be snapped - const float max_snap_height = 20.0; + const float max_snap_height = 500.0; // Will be set to `true` if at least one node from the selection was successfully snapped bool snapped_to_floor = false; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 5df6743f4c..677a5f1f2c 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -214,6 +214,7 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const { void ScriptEditorBase::_bind_methods() { ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor); + ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptEditorBase::add_syntax_highlighter); ADD_SIGNAL(MethodInfo("name_changed")); ADD_SIGNAL(MethodInfo("edited_script_changed")); @@ -762,20 +763,24 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected); if (current) { - Ref<Script> script = current->get_edited_resource(); - if (p_save && script.is_valid()) { + RES file = current->get_edited_resource(); + if (p_save && file.is_valid()) { // Do not try to save internal scripts, but prompt to save in-memory // scripts which are not saved to disk yet (have empty path). - if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) { + if (file->get_path().find("local://") == -1 && file->get_path().find("::") == -1) { save_current_script(); } } - if (script.is_valid()) { - if (!script->get_path().is_empty()) { + if (file.is_valid()) { + if (!file->get_path().is_empty()) { // Only saved scripts can be restored. - previous_scripts.push_back(script->get_path()); + previous_scripts.push_back(file->get_path()); + } + + Ref<Script> script = file; + if (script.is_valid()) { + notify_script_close(script); } - notify_script_close(script); } } @@ -1079,7 +1084,6 @@ void ScriptEditor::_file_dialog_action(String p_file) { memdelete(file); if (EditorFileSystem::get_singleton()) { - const Vector<String> textfile_extensions = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); if (textfile_extensions.has(p_file.get_extension())) { EditorFileSystem::get_singleton()->update_file(p_file); } @@ -1167,9 +1171,8 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog_option = FILE_NEW_TEXTFILE; file_dialog->clear_filters(); - const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); - for (int i = 0; i < textfile_ext.size(); i++) { - file_dialog->add_filter("*." + textfile_ext[i] + " ; " + textfile_ext[i].to_upper()); + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E + " ; " + E.to_upper()); } file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("New Text File...")); @@ -1187,9 +1190,8 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } - const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); - for (int i = 0; i < textfile_ext.size(); i++) { - file_dialog->add_filter("*." + textfile_ext[i] + " ; " + textfile_ext[i].to_upper()); + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E + " ; " + E.to_upper()); } file_dialog->popup_file_dialog(); @@ -1542,6 +1544,7 @@ void ScriptEditor::_notification(int p_what) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); + _editor_settings_changed(); [[fallthrough]]; } case NOTIFICATION_TRANSLATION_CHANGED: @@ -2117,7 +2120,7 @@ void ScriptEditor::_update_script_connections() { } } -Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) { +Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } @@ -2601,6 +2604,12 @@ void ScriptEditor::_save_layout() { } void ScriptEditor::_editor_settings_changed() { + textfile_extensions.clear(); + const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + for (const String &E : textfile_ext) { + textfile_extensions.insert(E); + } + trim_trailing_whitespace_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/trim_trailing_whitespace_on_save"); convert_indent_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/convert_indent_on_save"); use_space_indentation = EditorSettings::get_singleton()->get("text_editor/behavior/indent/type"); @@ -2807,12 +2816,22 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data if (file == "" || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - return true; + if (ResourceLoader::exists(file, "Script")) { + Ref<Script> scr = ResourceLoader::load(file); + if (scr.is_valid()) { + return true; + } + } + + if (textfile_extensions.has(file.get_extension())) { + Error err; + Ref<TextFile> text_file = _load_text_file(file, &err); + if (text_file.is_valid() && err == OK) { + return true; + } } } - return true; + return false; } return false; @@ -2877,9 +2896,13 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co if (file == "" || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - edit(scr); + + if (!ResourceLoader::exists(file, "Script") && !textfile_extensions.has(file.get_extension())) { + continue; + } + + RES res = open_file(file); + if (res.is_valid()) { if (tab_container->get_child_count() > num_tabs_before) { tab_container->move_child(tab_container->get_child(tab_container->get_child_count() - 1), new_index); num_tabs_before = tab_container->get_child_count(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index ce26699280..8caebc1c8c 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -453,7 +453,8 @@ class ScriptEditor : public PanelContainer { Ref<Script> _get_current_script(); Array _get_open_scripts() const; - Ref<TextFile> _load_text_file(const String &p_path, Error *r_error); + Set<String> textfile_extensions; + Ref<TextFile> _load_text_file(const String &p_path, Error *r_error) const; Error _save_text_file(Ref<TextFile> p_text_file, const String &p_path); void _on_find_in_files_requested(String text); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 1c251075de..2c02389db2 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1341,8 +1341,6 @@ void ScriptTextEditor::_bind_methods() { ClassDB::bind_method("_get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw); ClassDB::bind_method("_can_drop_data_fw", &ScriptTextEditor::can_drop_data_fw); ClassDB::bind_method("_drop_data_fw", &ScriptTextEditor::drop_data_fw); - - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptTextEditor::add_syntax_highlighter); } Control *ScriptTextEditor::get_edit_menu() { diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 531ffc6a73..708eaf2c46 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -49,296 +49,128 @@ void BoneTransformEditor::create_editors() { section = memnew(EditorInspectorSection); section->setup("trf_properties", label, this, section_color, true); + section->unfold(); add_child(section); - enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled"))); - enabled_checkbox->set_flat(true); - enabled_checkbox->set_visible(toggle_enabled); + enabled_checkbox = memnew(EditorPropertyCheck()); + enabled_checkbox->set_label("Pose Enabled"); + enabled_checkbox->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); section->get_vbox()->add_child(enabled_checkbox); - key_button = memnew(Button); - key_button->set_text(TTR("Key Transform")); - key_button->set_visible(keyable); - key_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); - key_button->set_flat(true); - section->get_vbox()->add_child(key_button); - - // 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->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); - section->get_vbox()->add_child(translation_property); + // Position property. + position_property = memnew(EditorPropertyVector3()); + position_property->setup(-10000, 10000, 0.001f, true); + position_property->set_label("Position"); + position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); + section->get_vbox()->add_child(position_property); // Rotation property. - rotation_property = memnew(EditorPropertyVector3()); + rotation_property = memnew(EditorPropertyQuaternion()); rotation_property->setup(-10000, 10000, 0.001f, true); - rotation_property->set_label("Rotation Degrees"); - rotation_property->set_use_folding(true); - rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + rotation_property->set_label("Rotation"); + rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); 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->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); 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); + rest_section = memnew(EditorInspectorSection); + rest_section->setup("trf_properties_transform", "Rest", this, section_color, true); + section->get_vbox()->add_child(rest_section); // Transform/Matrix property. - transform_property = memnew(EditorPropertyTransform3D()); - transform_property->setup(-10000, 10000, 0.001f, true); - transform_property->set_label("Transform"); - transform_property->set_use_folding(true); - transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform)); - transform_section->get_vbox()->add_child(transform_property); + rest_matrix = memnew(EditorPropertyTransform3D()); + rest_matrix->setup(-10000, 10000, 0.001f, true); + rest_matrix->set_label("Transform"); + rest_section->get_vbox()->add_child(rest_matrix); } void BoneTransformEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { create_editors(); - key_button->connect("pressed", callable_mp(this, &BoneTransformEditor::_key_button_pressed)); - enabled_checkbox->connect("pressed", callable_mp(this, &BoneTransformEditor::_checkbox_pressed)); - [[fallthrough]]; - } - case NOTIFICATION_SORT_CHILDREN: { - const Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree")); - - Point2 buffer; - buffer.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - buffer.y += font->get_height(font_size); - buffer.y += get_theme_constant(SNAME("vseparation"), SNAME("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_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - Vector<Rect2> 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_theme_color(SNAME("dark_color_2"), SNAME("Editor")); - - for (int i = 0; i < 5; ++i) { - draw_rect(background_rects[i], dark_color); - } - break; } } } -void BoneTransformEditor::_value_changed(const double p_value) { +void BoneTransformEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { if (updating) { return; } - - Transform3D 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; - } - Transform3D tform = compute_transform_from_vector3s(); - _change_transform(tform); -} - -Transform3D 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 Transform3D( - Basis(prop_rotation, scale_property->get_vector()), - translation_property->get_vector()); -} - -void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean) { - if (updating) { - return; - } - _change_transform(p_transform); -} - -void BoneTransformEditor::_change_transform(Transform3D 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") { + if (skeleton) { 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->add_undo_property(skeleton, p_property, skeleton->get(p_property)); + undo_redo->add_do_property(skeleton, p_property, p_value); 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::_update_properties() { - if (updating) { - return; - } - - if (!skeleton) { - return; - } - - updating = true; - - Transform3D tform = skeleton->get(property); - _update_transform_properties(tform); -} - -void BoneTransformEditor::_update_custom_pose_properties() { - if (updating) { - return; - } - - if (!skeleton) { - return; - } - - updating = true; - - Transform3D tform = skeleton->get_bone_custom_pose(property.to_int()); - _update_transform_properties(tform); -} - -void BoneTransformEditor::_update_transform_properties(Transform3D 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(Skeleton3D *p_skeleton) : skeleton(p_skeleton) { 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; -} + enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled"); + enabled_checkbox->update_property(); -void BoneTransformEditor::_update_key_button(const bool p_keyable) { - bool is_keyable = keyable && p_keyable; - if (key_button) { - key_button->set_visible(is_keyable); - } -} + position_property->set_object_and_property(skeleton, p_prop + "position"); + position_property->update_property(); -void BoneTransformEditor::set_properties_read_only(const bool p_readonly) { - enabled_checkbox->set_disabled(p_readonly); - enabled_checkbox->update(); -} + rotation_property->set_object_and_property(skeleton, p_prop + "rotation"); + rotation_property->update_property(); -void BoneTransformEditor::set_transform_read_only(const bool p_readonly) { - translation_property->set_read_only(p_readonly); - rotation_property->set_read_only(p_readonly); - scale_property->set_read_only(p_readonly); - transform_property->set_read_only(p_readonly); - translation_property->update(); - rotation_property->update(); - scale_property->update(); - transform_property->update(); - _update_key_button(!p_readonly); -} + scale_property->set_object_and_property(skeleton, p_prop + "scale"); + scale_property->update_property(); -void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { - toggle_enabled = p_enabled; - if (enabled_checkbox) { - enabled_checkbox->set_visible(p_enabled); - } + rest_matrix->set_object_and_property(skeleton, p_prop + "rest"); + rest_matrix->update_property(); } -void BoneTransformEditor::_key_button_pressed() { - if (!skeleton) { - return; - } - - const BoneId bone_id = property.get_slicec('/', 1).to_int(); - const String name = skeleton->get_bone_name(bone_id); - - if (name.is_empty()) { - return; - } - - Transform3D tform = compute_transform_from_vector3s(); - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(skeleton, name, tform); -} - -void BoneTransformEditor::_checkbox_pressed() { +void BoneTransformEditor::_update_properties() { if (!skeleton) { return; } - - const BoneId bone_id = property.get_slicec('/', 1).to_int(); - if (enabled_checkbox) { - undo_redo->create_action(TTR("Set Pose Enabled")); - bool enabled = skeleton->is_bone_enabled(bone_id); - undo_redo->add_do_method(skeleton, "set_bone_enabled", bone_id, !enabled); - undo_redo->add_undo_method(skeleton, "set_bone_enabled", bone_id, enabled); - undo_redo->commit_action(); + int selected = Skeleton3DEditor::get_singleton()->get_selected_bone(); + List<PropertyInfo> props; + skeleton->get_property_list(&props); + for (const PropertyInfo &E : props) { + PackedStringArray spr = E.name.split("/"); + if (spr.size() == 3 && spr[0] == "bones") { + if (spr[1].to_int() == selected) { + if (spr[2] == "enabled") { + enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + enabled_checkbox->update_property(); + enabled_checkbox->update(); + } + if (spr[2] == "position") { + position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + position_property->update_property(); + position_property->update(); + } + if (spr[2] == "rotation") { + rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + rotation_property->update_property(); + rotation_property->update(); + } + if (spr[2] == "scale") { + scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + scale_property->update_property(); + scale_property->update(); + } + if (spr[2] == "rest") { + rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + rest_matrix->update_property(); + rest_matrix->update(); + } + } + } } } @@ -354,24 +186,6 @@ void Skeleton3DEditor::set_rest_options_enabled(const bool p_rest_options_enable rest_options->get_popup()->set_item_disabled(REST_OPTION_POSE_TO_REST, !p_rest_options_enabled); }; -void Skeleton3DEditor::_update_show_rest_only() { - _update_pose_enabled(-1); -} - -void Skeleton3DEditor::_update_pose_enabled(int p_bone) { - if (!skeleton) { - return; - } - if (pose_editor) { - pose_editor->set_properties_read_only(skeleton->is_show_rest_only()); - - if (selected_bone > 0) { - pose_editor->set_transform_read_only(skeleton->is_show_rest_only() || !(skeleton->is_bone_enabled(selected_bone))); - } - } - _update_gizmo_visible(); -} - void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { if (!skeleton) { return; @@ -418,8 +232,13 @@ void Skeleton3DEditor::init_pose() { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); for (int i = 0; i < bone_len; i++) { - ur->add_do_method(skeleton, "set_bone_pose", i, Transform3D()); - ur->add_undo_method(skeleton, "set_bone_pose", i, skeleton->get_bone_pose(i)); + Transform3D rest = skeleton->get_bone_rest(i); + ur->add_do_method(skeleton, "set_bone_pose_position", i, rest.origin); + ur->add_do_method(skeleton, "set_bone_pose_rotation", i, rest.basis.get_rotation_quaternion()); + ur->add_do_method(skeleton, "set_bone_pose_scale", i, rest.basis.get_scale()); + ur->add_undo_method(skeleton, "set_bone_pose_position", i, skeleton->get_bone_pose_position(i)); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", i, skeleton->get_bone_pose_rotation(i)); + ur->add_undo_method(skeleton, "set_bone_pose_scale", i, skeleton->get_bone_pose_scale(i)); } ur->commit_action(); } @@ -459,15 +278,9 @@ void Skeleton3DEditor::pose_to_rest() { // Todo: Do method with multiple bone selection. UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); - - ur->add_do_method(skeleton, "set_bone_pose", selected_bone, Transform3D()); - ur->add_undo_method(skeleton, "set_bone_pose", selected_bone, skeleton->get_bone_pose(selected_bone)); - ur->add_do_method(skeleton, "set_bone_custom_pose", selected_bone, Transform3D()); - ur->add_undo_method(skeleton, "set_bone_custom_pose", selected_bone, skeleton->get_bone_custom_pose(selected_bone)); - ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone) * skeleton->get_bone_custom_pose(selected_bone) * skeleton->get_bone_pose(selected_bone)); + ur->create_action(TTR("Set Bone Rest"), UndoRedo::MERGE_ENDS); + ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone)); ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone)); - ur->commit_action(); } @@ -652,20 +465,14 @@ void Skeleton3DEditor::_joint_tree_selection_changed() { 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); - + pose_editor->set_target(bone_path); selected_bone = b_idx; } } + pose_editor->set_visible(selected); set_rest_options_enabled(selected); _update_properties(); - _update_pose_enabled(); + _update_gizmo_visible(); } // May be not used with single select mode. @@ -673,16 +480,10 @@ void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { } void Skeleton3DEditor::_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(); - } - _update_gizmo_transform(); + Node3DEditor::get_singleton()->update_transform_gizmo(); } void Skeleton3DEditor::update_joint_tree() { @@ -809,23 +610,9 @@ void Skeleton3DEditor::create_editors() { s_con->add_child(joint_tree); pose_editor = memnew(BoneTransformEditor(skeleton)); - pose_editor->set_label(TTR("Bone Pose")); - pose_editor->set_toggle_enabled(true); - pose_editor->set_keyable(te->has_keying()); + pose_editor->set_label(TTR("Bone Transform")); 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); - rest_editor->set_transform_read_only(true); - - 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); - custom_pose_editor->set_transform_read_only(true); } void Skeleton3DEditor::_notification(int p_what) { @@ -844,8 +631,8 @@ void Skeleton3DEditor::_notification(int p_what) { #ifdef TOOLS_ENABLED skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); - skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled)); - skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only)); + skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); + skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); #endif break; } @@ -866,8 +653,6 @@ void Skeleton3DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_node_removed"), &Skeleton3DEditor::_node_removed); ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &Skeleton3DEditor::_joint_tree_selection_changed); ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select); - ClassDB::bind_method(D_METHOD("_update_show_rest_only"), &Skeleton3DEditor::_update_show_rest_only); - ClassDB::bind_method(D_METHOD("_update_pose_enabled"), &Skeleton3DEditor::_update_pose_enabled); ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties); ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option); ClassDB::bind_method(D_METHOD("_on_click_rest_option"), &Skeleton3DEditor::_on_click_rest_option); @@ -938,7 +723,9 @@ void Skeleton3DEditor::update_bone_original() { if (skeleton->get_bone_count() == 0 || selected_bone == -1) { return; } - bone_original = skeleton->get_bone_pose(selected_bone); + bone_original_position = skeleton->get_bone_pose_position(selected_bone); + bone_original_rotation = skeleton->get_bone_pose_rotation(selected_bone); + bone_original_scale = skeleton->get_bone_pose_scale(selected_bone); } void Skeleton3DEditor::_hide_handles() { @@ -1066,8 +853,8 @@ void Skeleton3DEditor::select_bone(int p_idx) { Skeleton3DEditor::~Skeleton3DEditor() { if (skeleton) { #ifdef TOOLS_ENABLED - skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only)); - skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled)); + skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); + skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); skeleton->set_transform_gizmo_visible(true); @@ -1148,9 +935,9 @@ bool Skeleton3DEditorPlugin::handles(Object *p_object) const { return p_object->is_class("Skeleton3D"); } -void Skeleton3DEditor::_update_gizmo_transform() { - Node3DEditor::get_singleton()->update_transform_gizmo(); -}; +void Skeleton3DEditor::_bone_enabled_changed(const int p_bone_id) { + _update_gizmo_visible(); +} void Skeleton3DEditor::_update_gizmo_visible() { _subgizmo_selection_change(); @@ -1289,7 +1076,6 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi if (parent_idx >= 0) { original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); } - original_to_local = original_to_local * skeleton->get_bone_rest(p_id) * skeleton->get_bone_custom_pose(p_id); Basis to_local = original_to_local.get_basis().inverse(); // Prepare transform. @@ -1305,7 +1091,9 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi t.origin = orig + to_local.xform(sub); // Apply transform. - skeleton->set_bone_pose(p_id, t); + skeleton->set_bone_pose_position(p_id, t.origin); + skeleton->set_bone_pose_rotation(p_id, t.basis.get_rotation_quaternion()); + skeleton->set_bone_pose_scale(p_id, t.basis.get_scale()); } void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { @@ -1313,12 +1101,30 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c ERR_FAIL_COND(!skeleton); Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - for (int i = 0; i < p_ids.size(); i++) { - ur->create_action(TTR("Set Bone Transform")); - ur->add_do_method(skeleton, "set_bone_pose", p_ids[i], skeleton->get_bone_pose(p_ids[i])); - ur->add_undo_method(skeleton, "set_bone_pose", p_ids[i], se->get_bone_original()); + ur->create_action(TTR("Set Bone Transform")); + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { + for (int i = 0; i < p_ids.size(); i++) { + ur->add_do_method(skeleton, "set_bone_pose_position", p_ids[i], skeleton->get_bone_pose_position(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_position", p_ids[i], se->get_bone_original_position()); + } + } + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { + for (int i = 0; i < p_ids.size(); i++) { + ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation()); + } + } + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) { + for (int i = 0; i < p_ids.size(); i++) { + // If the axis is swapped by scaling, the rotation can be changed. + ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation()); + ur->add_do_method(skeleton, "set_bone_pose_scale", p_ids[i], skeleton->get_bone_pose_scale(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale()); + } } ur->commit_action(); } @@ -1516,5 +1322,5 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(Ref<Skin>())); + p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index e2a1d9a628..3b4dd362fb 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -52,21 +52,22 @@ class BoneTransformEditor : public VBoxContainer { EditorInspectorSection *section = nullptr; - EditorPropertyVector3 *translation_property = nullptr; - EditorPropertyVector3 *rotation_property = nullptr; + EditorPropertyCheck *enabled_checkbox = nullptr; + EditorPropertyVector3 *position_property = nullptr; + EditorPropertyQuaternion *rotation_property = nullptr; EditorPropertyVector3 *scale_property = nullptr; - EditorInspectorSection *transform_section = nullptr; - EditorPropertyTransform3D *transform_property = nullptr; + + EditorInspectorSection *rest_section = nullptr; + EditorPropertyTransform3D *rest_matrix = nullptr; Rect2 background_rects[5]; Skeleton3D *skeleton; - String property; + // String property; UndoRedo *undo_redo; - Button *key_button = nullptr; - CheckBox *enabled_checkbox = nullptr; + // Button *key_button = nullptr; bool keyable = false; bool toggle_enabled = false; @@ -76,20 +77,7 @@ class BoneTransformEditor : public VBoxContainer { 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 Transform3D 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(Transform3D p_new_transform); - // Update it is truely keyable then. - void _update_key_button(const bool p_keyable); - // Creates a Transform using the EditorPropertyVector3 properties. - Transform3D compute_transform_from_vector3s() const; - - void update_enabled_checkbox(); + void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); protected: void _notification(int p_what); @@ -102,24 +90,6 @@ public: void set_label(const String &p_label) { label = p_label; } void _update_properties(); - void _update_custom_pose_properties(); - void _update_transform_properties(Transform3D p_transform); - - // Transform can be keyed, whether or not to show the button. - void set_keyable(const bool p_keyable); - - // When rest mode, pose and custom_pose editor are diasbled. - void set_properties_read_only(const bool p_readonly); - void set_transform_read_only(const bool p_readonly); - - // 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_pressed(); }; class Skeleton3DEditor : public VBoxContainer { @@ -151,7 +121,6 @@ class Skeleton3DEditor : public VBoxContainer { Tree *joint_tree = nullptr; BoneTransformEditor *rest_editor = nullptr; BoneTransformEditor *pose_editor = nullptr; - BoneTransformEditor *custom_pose_editor = nullptr; VSeparator *separator; MenuButton *skeleton_options = nullptr; @@ -199,13 +168,12 @@ class Skeleton3DEditor : public VBoxContainer { Ref<ShaderMaterial> handle_material; Ref<Shader> handle_shader; - Transform3D bone_original; - - void _update_pose_enabled(int p_bone = -1); - void _update_show_rest_only(); + Vector3 bone_original_position; + Quaternion bone_original_rotation; + Vector3 bone_original_scale; - void _update_gizmo_transform(); void _update_gizmo_visible(); + void _bone_enabled_changed(const int p_bone_id); void _hide_handles(); @@ -239,7 +207,9 @@ public: bool is_edit_mode() const { return edit_mode; } void update_bone_original(); - Transform3D get_bone_original() { return bone_original; }; + Vector3 get_bone_original_position() const { return bone_original_position; }; + Quaternion get_bone_original_rotation() const { return bone_original_rotation; }; + Vector3 get_bone_original_scale() const { return bone_original_scale; }; Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton); ~Skeleton3DEditor(); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 06ba8a6168..1fc7eb98e0 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -407,10 +407,6 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } -void TextEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &TextEditor::add_syntax_highlighter); -} - static ScriptEditorBase *create_editor(const RES &p_resource) { if (Object::cast_to<TextFile>(*p_resource)) { return memnew(TextEditor); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 9308fec210..7404557f46 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -87,8 +87,6 @@ private: }; protected: - static void _bind_methods(); - void _edit_option(int p_op); void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position); void _text_edit_gui_input(const Ref<InputEvent> &ev); diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index f21d5098d3..c064073b77 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -97,15 +97,6 @@ Size2i TileAtlasView::_compute_base_tiles_control_size() { if (texture.is_valid()) { size = texture->get_size(); } - - // Extend the size to all existing tiles. - Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { - Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); - grid_size = grid_size.max(tile_id + Vector2i(1, 1)); - } - size = size.max(grid_size * (tile_set_atlas_source->get_texture_region_size() + tile_set_atlas_source->get_separation()) + tile_set_atlas_source->get_margins()); - return size; } @@ -213,43 +204,56 @@ void TileAtlasView::_draw_base_tiles() { Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); if (texture.is_valid()) { Vector2i margins = tile_set_atlas_source->get_margins(); + Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - - // Draw the texture, square by square. Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + + // Draw the texture where there is no tile. for (int x = 0; x < grid_size.x; x++) { for (int y = 0; y < grid_size.y; y++) { Vector2i coords = Vector2i(x, y); if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { - Rect2i rect = Rect2i(texture_region_size * coords + margins, texture_region_size); - base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation); + rect = rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (rect.size.x > 0 && rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + } } } } // Draw the texture around the grid. Rect2i rect; + // Top. rect.position = Vector2i(); rect.set_end(Vector2i(texture->get_size().x, margins.y)); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + // Bottom - int bottom_border = margins.y + (grid_size.y * texture_region_size.y); + int bottom_border = margins.y + (grid_size.y * (texture_region_size.y + separation.y)); if (bottom_border < texture->get_size().y) { rect.position = Vector2i(0, bottom_border); rect.set_end(texture->get_size()); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } + // Left rect.position = Vector2i(0, margins.y); - rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y))); + rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * (texture_region_size.y + separation.y)))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + // Right. - int right_border = margins.x + (grid_size.x * texture_region_size.x); + int right_border = margins.x + (grid_size.x * (texture_region_size.x + separation.x)); if (right_border < texture->get_size().x) { rect.position = Vector2i(right_border, margins.y); - rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y))); + rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * (texture_region_size.y + separation.y)))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } // Draw actual tiles, using their properties (modulation, etc...) @@ -258,12 +262,30 @@ void TileAtlasView::_draw_base_tiles() { for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) { // Update the y to max value. - int animation_columns = tile_set_atlas_source->get_tile_animation_columns(atlas_coords); - Vector2i frame_coords = atlas_coords + (tile_set_atlas_source->get_tile_size_in_atlas(atlas_coords) + tile_set_atlas_source->get_tile_animation_separation(atlas_coords)) * ((animation_columns > 0) ? Vector2i(frame % animation_columns, frame / animation_columns) : Vector2i(frame, 0)); - Vector2i offset_pos = (margins + (frame_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0)); + Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame); + Vector2i offset_pos = base_frame_rect.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0); // Draw the tile. TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame); + + // Draw, the texture in the separation areas + if (separation.x > 0) { + Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y)); + right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect); + base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } + + if (separation.y > 0) { + Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y)); + bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect); + base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } } } } @@ -299,30 +321,6 @@ void TileAtlasView::_draw_base_tiles_texture_grid() { } } -void TileAtlasView::_draw_base_tiles_dark() { - Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); - if (texture.is_valid()) { - Vector2i margins = tile_set_atlas_source->get_margins(); - Vector2i separation = tile_set_atlas_source->get_separation(); - Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - - Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - - // Draw each tile texture region. - for (int x = 0; x < grid_size.x; x++) { - for (int y = 0; y < grid_size.y; y++) { - Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation)); - Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); - - if (base_tile_coords == TileSetSource::INVALID_ATLAS_COORDS) { - // Draw the grid. - base_tiles_dark->draw_rect(Rect2i(origin, texture_region_size), Color(0.0, 0.0, 0.0, 0.5), true); - } - } - } - } -} - void TileAtlasView::_draw_base_tiles_shape_grid() { // Draw the shapes. Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); @@ -453,7 +451,6 @@ void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_ base_tiles_draw->update(); base_tiles_texture_grid->update(); base_tiles_shape_grid->update(); - base_tiles_dark->update(); alternatives_draw->update(); background_left->update(); background_right->update(); @@ -544,7 +541,6 @@ void TileAtlasView::update() { base_tiles_draw->update(); base_tiles_texture_grid->update(); base_tiles_shape_grid->update(); - base_tiles_dark->update(); alternatives_draw->update(); background_left->update(); background_right->update(); @@ -660,12 +656,6 @@ TileAtlasView::TileAtlasView() { base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid)); base_tiles_drawing_root->add_child(base_tiles_shape_grid); - base_tiles_dark = memnew(Control); - base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark)); - base_tiles_drawing_root->add_child(base_tiles_dark); - // Alternative tiles. Label *alternative_tiles_label = memnew(Label); alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index 5b0df366ae..e1ca3eebee 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -93,9 +93,6 @@ private: Control *base_tiles_shape_grid; void _draw_base_tiles_shape_grid(); - Control *base_tiles_dark; - void _draw_base_tiles_dark(); - Size2i _compute_base_tiles_control_size(); // Right side. @@ -124,7 +121,6 @@ public: // Left side. void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); }; - void set_dark_visible(bool p_visible) { base_tiles_dark->set_visible(p_visible); }; void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); }; Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos) const; diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 1a69d19d3c..104f46f773 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -38,8 +38,16 @@ #include "editor/editor_properties.h" #include "editor/editor_scale.h" -void TileDataEditor::_call_tile_set_changed() { - _tile_set_changed(); +void TileDataEditor::_tile_set_changed_plan_update() { + _tile_set_changed_update_needed = true; + call_deferred("_tile_set_changed_deferred_update"); +} + +void TileDataEditor::_tile_set_changed_deferred_update() { + if (_tile_set_changed_update_needed) { + _tile_set_changed(); + _tile_set_changed_update_needed = false; + } } TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { @@ -59,18 +67,20 @@ TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { } void TileDataEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileDataEditor::_tile_set_changed_deferred_update); + ADD_SIGNAL(MethodInfo("needs_redraw")); } void TileDataEditor::set_tile_set(Ref<TileSet> p_tile_set) { if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } tile_set = p_tile_set; if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + tile_set->connect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } - _call_tile_set_changed(); + _tile_set_changed_plan_update(); } bool DummyObject::_set(const StringName &p_name, const Variant &p_value) { diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index 99998dc779..acb4c5882d 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -45,7 +45,9 @@ class TileDataEditor : public VBoxContainer { GDCLASS(TileDataEditor, VBoxContainer); private: - void _call_tile_set_changed(); + bool _tile_set_changed_update_needed = false; + void _tile_set_changed_plan_update(); + void _tile_set_changed_deferred_update(); protected: Ref<TileSet> tile_set; diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index cc5ff90541..5ca39b82b2 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -456,7 +456,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p case DRAG_TYPE_PAINT: { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -465,6 +465,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } + _fix_invalid_tiles_in_tile_map_selection(); } break; case DRAG_TYPE_BUCKET: { Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); @@ -472,7 +473,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -483,6 +484,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } } + _fix_invalid_tiles_in_tile_map_selection(); } break; default: break; @@ -499,8 +501,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(mb->get_position()); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { if (mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + rmb_erasing = true; + } + // Pressed if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) { // Do nothing. @@ -508,6 +514,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p drag_start_mouse_pos = mpos; if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) { // Move the selection + _update_selection_pattern_from_tilemap_selection(); // Make sure the pattern is up to date before moving. drag_type = DRAG_TYPE_MOVE; drag_modified.clear(); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { @@ -521,18 +528,18 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } else { // Check if we are picking a tile. - if (picker_button->is_pressed()) { + if (picker_button->is_pressed() || (Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { drag_type = DRAG_TYPE_PICK; drag_start_mouse_pos = mpos; } else { // Paint otherwise. - if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + if (tool_buttons_group->get_pressed_button() == paint_tool_button && !Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { drag_type = DRAG_TYPE_PAINT; drag_start_mouse_pos = mpos; drag_modified.clear(); Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -541,11 +548,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + _fix_invalid_tiles_in_tile_map_selection(); + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(KEY_SHIFT) && !Input::get_singleton()->is_key_pressed(KEY_CTRL))) { drag_type = DRAG_TYPE_LINE; drag_start_mouse_pos = mpos; drag_modified.clear(); - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(KEY_SHIFT) && Input::get_singleton()->is_key_pressed(KEY_CTRL))) { drag_type = DRAG_TYPE_RECT; drag_start_mouse_pos = mpos; drag_modified.clear(); @@ -558,7 +566,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -569,6 +577,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } } + _fix_invalid_tiles_in_tile_map_selection(); } } } @@ -576,6 +585,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } else { // Released _stop_dragging(); + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + rmb_erasing = false; + } } CanvasItemEditor::get_singleton()->update_viewport(); @@ -682,13 +694,13 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(drag_last_mouse_pos - mouse_offset), clipboard_used_cells[i], tile_map_clipboard); preview[coords] = TileMapCell(tile_map_clipboard->get_cell_source_id(clipboard_used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(clipboard_used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(clipboard_used_cells[i])); } - } else if (!picker_button->is_pressed()) { + } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { bool expand_grid = false; if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos); expand_grid = true; - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || drag_type == DRAG_TYPE_LINE) { if (drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos); @@ -698,12 +710,12 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over preview = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, drag_last_mouse_pos); expand_grid = true; } - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button && drag_type == DRAG_TYPE_RECT) { - // Preview for a line pattern. + } else if (drag_type == DRAG_TYPE_RECT) { + // Preview for a rect pattern. preview = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos)); expand_grid = true; } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) { - // Preview for a line pattern. + // Preview for a fill pattern. preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_continuous_checkbox->is_pressed()); } @@ -752,7 +764,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Transform2D tile_xform; tile_xform.set_origin(tile_map->map_to_world(E.key)); tile_xform.set_scale(tile_set->get_tile_size()); - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); } else { if (tile_set->has_source(E.value.source_id)) { @@ -877,12 +889,12 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { // Paint the tiles on the tile map. - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(p_from_mouse_pos), tile_map->world_to_map(p_to_mouse_pos)); for (int i = 0; i < line.size(); i++) { @@ -929,7 +941,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> err_output; ERR_FAIL_COND_V(pattern->is_empty(), err_output); @@ -940,7 +952,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start Map<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. for (int x = 0; x < rect.size.x; x++) { for (int y = 0; y < rect.size.y; y++) { @@ -988,7 +1000,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; if (!pattern->is_empty()) { TileMapCell source = tile_map->get_cell(tile_map_layer, p_coords); @@ -1012,7 +1024,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1058,7 +1070,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1215,7 +1227,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos); undo_redo->create_action(TTR("Paint tiles")); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); @@ -1227,7 +1239,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos)); undo_redo->create_action(TTR("Paint tiles")); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); @@ -1323,6 +1335,29 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { } } +void TileMapEditorTilesPlugin::_fix_invalid_tiles_in_tile_map_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Set<Vector2i> to_remove; + for (Vector2i selected : tile_map_selection) { + TileMapCell cell = tile_map->get_cell(tile_map_layer, selected); + if (cell.source_id == TileSet::INVALID_SOURCE && cell.get_atlas_coords() == TileSetSource::INVALID_ATLAS_COORDS && cell.alternative_tile == TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + to_remove.insert(selected); + } + } + + for (Vector2i cell : to_remove) { + tile_map_selection.erase(cell); + } +} + +bool TileMapEditorTilesPlugin::_is_erasing() const { + return erase_button->is_pressed() || rmb_erasing; +} + void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { @@ -1423,6 +1458,8 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( } } _update_bottom_panel(); + tile_atlas_control->update(); + alternative_tiles_control->update(); } void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { @@ -1804,6 +1841,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_D)); + paint_tool_button->set_tooltip(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle.")); paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); @@ -1844,6 +1882,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { picker_button->set_flat(true); picker_button->set_toggle_mode(true); picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->set_tooltip(TTR("Alternatively hold Ctrl with other tools to pick tile.")); picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); @@ -1852,6 +1891,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { erase_button->set_flat(true); erase_button->set_toggle_mode(true); erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + erase_button->set_tooltip(TTR("Alternatively use RMB to erase tiles.")); erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 6126db59e9..5fbd9cada8 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -104,12 +104,14 @@ private: Vector2 drag_start_mouse_pos; Vector2 drag_last_mouse_pos; Map<Vector2i, TileMapCell> drag_modified; + bool rmb_erasing = false; TileMapCell _pick_random_tile(const TileMapPattern *p_pattern); Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos); Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell); Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous); void _stop_dragging(); + bool _is_erasing() const; ///// Selection system. ///// Set<Vector2i> tile_map_selection; @@ -124,6 +126,7 @@ private: void _update_selection_pattern_from_tileset_selection(); void _update_tileset_selection_from_selection_pattern(); void _update_fix_selected_and_hovered(); + void _fix_invalid_tiles_in_tile_map_selection(); ///// Bottom panel. ////. Label *missing_source_label; diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 3fbd315aec..c43a854d9a 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -520,9 +520,6 @@ void TileSetAtlasSourceEditor::_update_tile_id_label() { void TileSetAtlasSourceEditor::_update_source_inspector() { // Update the proxy object. atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id); - - // Update the "clear outside texture" button. - tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, !tile_set_atlas_source->has_tiles_outside_texture()); } void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() { @@ -763,7 +760,11 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { // Update visibility. bool is_visible = tools_button_group->get_pressed_button() == tool_paint_button; tile_data_editor_dropdown_button->set_visible(is_visible); - tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); + if (tile_data_editors_tree->get_selected()) { + tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0)); + } else { + tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); + } tile_data_editors_label->set_visible(is_visible); } @@ -959,7 +960,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven current_tile_data_editor->forward_painting_atlas_gui_input(tile_atlas_view, tile_set_atlas_source, p_event); } // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; tile_atlas_control->update(); tile_atlas_control_unscaled->update(); @@ -1061,7 +1062,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_current_tile = coords; // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; _update_tile_inspector(); _update_atlas_view(); _update_tile_id_label(); @@ -1101,7 +1102,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_current_tile = new_rect.position; // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; _update_tile_inspector(); _update_atlas_view(); _update_tile_id_label(); @@ -1600,9 +1601,6 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) { undo_redo->commit_action(); _update_tile_id_label(); } break; - case ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE: { - tile_set_atlas_source->clear_tiles_outside_texture(); - } break; case ADVANCED_AUTO_CREATE_TILES: { _auto_create_tiles(); } break; @@ -2012,12 +2010,12 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { } } -void TileSetAtlasSourceEditor::_tile_set_atlas_source_changed() { - tile_set_atlas_source_changed_needs_update = true; +void TileSetAtlasSourceEditor::_tile_set_changed() { + tile_set_changed_needs_update = true; } void TileSetAtlasSourceEditor::_tile_proxy_object_changed(String p_what) { - tile_set_atlas_source_changed_needs_update = false; // Avoid updating too many things. + tile_set_changed_needs_update = false; // Avoid updating too many things. _update_atlas_view(); } @@ -2035,30 +2033,66 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo #define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); - AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); - if (tile_data) { + undo_redo->start_force_keep_in_merge_ends(); + AtlasTileProxyObject *tile_data_proxy = Object::cast_to<AtlasTileProxyObject>(p_edited); + if (tile_data_proxy) { Vector<String> components = String(p_property).split("/", true, 2); if (components.size() == 2 && components[1] == "polygons_count") { int layer_index = components[0].trim_prefix("physics_layer_").to_int(); int new_polygons_count = p_new_value; - int old_polygons_count = tile_data->get(vformat("physics_layer_%d/polygons_count", layer_index)); + int old_polygons_count = tile_data_proxy->get(vformat("physics_layer_%d/polygons_count", layer_index)); if (new_polygons_count < old_polygons_count) { for (int i = new_polygons_count - 1; i < old_polygons_count; i++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); } } } else if (p_property == "terrain_set") { - int current_terrain_set = tile_data->get("terrain_set"); + int current_terrain_set = tile_data_proxy->get("terrain_set"); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(current_terrain_set, bit)) { - ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); + ADD_UNDO(tile_data_proxy, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); } } } } + + TileSetAtlasSourceProxyObject *atlas_source_proxy = Object::cast_to<TileSetAtlasSourceProxyObject>(p_edited); + if (atlas_source_proxy) { + TileSetAtlasSource *atlas_source = atlas_source_proxy->get_edited(); + ERR_FAIL_COND(!atlas_source); + + PackedVector2Array arr; + if (p_property == "texture") { + arr = atlas_source->get_tiles_to_be_removed_on_change(p_new_value, atlas_source->get_margins(), atlas_source->get_separation(), atlas_source->get_texture_region_size()); + } else if (p_property == "margins") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), p_new_value, atlas_source->get_separation(), atlas_source->get_texture_region_size()); + } else if (p_property == "separation") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), p_new_value, atlas_source->get_texture_region_size()); + } else if (p_property == "texture_region_size") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), atlas_source->get_separation(), p_new_value); + } + + if (!arr.is_empty()) { + // Get all properties assigned to a tile. + List<PropertyInfo> properties; + atlas_source->get_property_list(&properties); + + for (int i = 0; i < arr.size(); i++) { + Vector2i coords = arr[i]; + String prefix = vformat("%d:%d/", coords.x, coords.y); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(prefix)) { + ADD_UNDO(atlas_source, pi.name); + } + } + } + } + } + undo_redo->end_force_keep_in_merge_ends(); + #undef ADD_UNDO } @@ -2073,8 +2107,8 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource } // Remove listener for old objects. - if (tile_set_atlas_source) { - tile_set_atlas_source->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed)); + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Clear the selection. @@ -2086,8 +2120,8 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource tile_set_atlas_source_id = p_source_id; // Add the listener again. - if (tile_set_atlas_source) { - tile_set_atlas_source->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed)); + if (tile_set.is_valid()) { + tile_set->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Update everything. @@ -2228,7 +2262,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons")); break; case NOTIFICATION_INTERNAL_PROCESS: - if (tile_set_atlas_source_changed_needs_update) { + if (tile_set_changed_needs_update) { // Update everything. _update_source_inspector(); @@ -2241,7 +2275,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { _update_tile_data_editors(); _update_current_tile_data_editor(); - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; } break; default: @@ -2406,8 +2440,6 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tool_advanced_menu_buttom = memnew(MenuButton); tool_advanced_menu_buttom->set_flat(true); - tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE); - tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, true); tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES); tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES); tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); @@ -2481,6 +2513,8 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL); tile_atlas_view_missing_source_label->hide(); right_panel->add_child(tile_atlas_view_missing_source_label); + + EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetAtlasSourceEditor::_undo_redo_inspector_callback)); } TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() { diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index 6448b55feb..ea6f2847ae 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -78,6 +78,7 @@ private: int get_id(); void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); + TileSetAtlasSource *get_edited() { return tile_set_atlas_source; }; }; // -- Proxy object for a tile, needed by the inspector -- @@ -112,7 +113,7 @@ private: UndoRedo *undo_redo = EditorNode::get_undo_redo(); - bool tile_set_atlas_source_changed_needs_update = false; + bool tile_set_changed_needs_update = false; // -- Properties painting -- VBoxContainer *tile_data_painting_editor_container; @@ -189,7 +190,6 @@ private: TILE_CREATE_ALTERNATIVE, TILE_DELETE, - ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE, ADVANCED_AUTO_CREATE_TILES, ADVANCED_AUTO_REMOVE_TILES, }; @@ -263,7 +263,7 @@ private: void _auto_remove_tiles(); AcceptDialog *confirm_auto_create_tiles; - void _tile_set_atlas_source_changed(); + void _tile_set_changed(); void _tile_proxy_object_changed(String p_what); void _atlas_source_proxy_object_changed(String p_what); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index b1b64564bb..48f64e041c 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -777,7 +777,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expand->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expand_output_port), varray(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); hb->add_child(expand); } - if (visual_shader->get_shader_type() == VisualShader::TYPE_FRAGMENT && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { + if (vsnode->has_output_port_preview(i) && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { TextureButton *preview = memnew(TextureButton); preview->set_toggle_mode(true); preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index 9062169e06..78dc85aa7d 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -433,7 +433,7 @@ void EditorSettingsDialog::_update_shortcuts() { } Array original = sc->get_meta("original"); - Array shortcuts_array = sc->get_events(); + Array shortcuts_array = sc->get_events().duplicate(true); bool same_as_defaults = Shortcut::is_event_array_equal(original, shortcuts_array); bool collapse = !collapsed.has(E) || (collapsed.has(E) && collapsed[E]); |