diff options
Diffstat (limited to 'editor')
137 files changed, 4896 insertions, 3840 deletions
diff --git a/editor/SCsub b/editor/SCsub index d149cc6273..e8d417319a 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -101,6 +101,7 @@ if env["tools"]: ) env.add_source_files(env.editor_sources, "*.cpp") + env.add_source_files(env.editor_sources, "register_exporters.gen.cpp") SConscript("debugger/SCsub") SConscript("fileserver/SCsub") 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_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 28642f1bb4..02b4a12b92 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -398,17 +398,17 @@ void AnimationBezierTrackEdit::_notification(int p_what) { float scale = timeline->get_zoom_scale(); Ref<Texture2D> point = get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")); - for (Map<int, Color>::Element *E = subtrack_colors.front(); E; E = E->next()) { - _draw_track(E->key(), E->get()); + for (const KeyValue<int, Color> &E : subtrack_colors) { + _draw_track(E.key, E.value); - for (int i = 0; i < animation->track_get_key_count(E->key()); i++) { - float offset = animation->track_get_key_time(E->key(), i); - float value = animation->bezier_track_get_key_value(E->key(), i); + for (int i = 0; i < animation->track_get_key_count(E.key); i++) { + float offset = animation->track_get_key_time(E.key, i); + float value = animation->bezier_track_get_key_value(E.key, i); Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); if (pos.x >= limit && pos.x <= right_limit) { - draw_texture(point, pos - point->get_size() / 2, E->get()); + draw_texture(point, pos - point->get_size() / 2, E.value); } } } @@ -680,9 +680,9 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { emit_signal(SNAME("close_request")); return; } - for (Map<int, Rect2>::Element *E = subtracks.front(); E; E = E->next()) { - if (E->get().has_point(mb->get_position())) { - set_animation_and_track(animation, E->key()); + for (const KeyValue<int, Rect2> &E : subtracks) { + if (E.value.has_point(mb->get_position())) { + set_animation_and_track(animation, E.key); _clear_selection(); return; } diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 324237ff82..7f118532c9 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -165,21 +165,40 @@ 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_BLEND_SHAPE: case Animation::TYPE_VALUE: { if (name == "value") { Variant value = p_value; @@ -412,13 +431,15 @@ 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_BLEND_SHAPE: case Animation::TYPE_VALUE: { if (name == "value") { r_ret = animation->track_get_key_value(track, key); @@ -523,11 +544,17 @@ 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_BLEND_SHAPE: { + p_list->push_back(PropertyInfo(Variant::FLOAT, "value")); } break; case Animation::TYPE_VALUE: { Variant v = animation->track_get_key_value(track, key); @@ -541,7 +568,7 @@ public: String hint_string; if (v.get_type() == Variant::OBJECT) { - //could actually check the object property if exists..? yes i will! + // Could actually check the object property if exists..? Yes I will! Ref<Resource> res = v; if (res.is_valid()) { hint = PROPERTY_HINT_RESOURCE_TYPE; @@ -700,16 +727,15 @@ public: return; } - for (Map<int, List<float>>::Element *E = key_ofs_map.front(); E; E = E->next()) { + for (const KeyValue<int, List<float>> &E : key_ofs_map) { int key = 0; - for (float &F : E->value()) { - float key_ofs = F; + for (const float &key_ofs : E.value) { if (from != key_ofs) { key++; continue; } - int track = E->key(); + int track = E.key; key_ofs_map[track][key] = to; if (setting) { @@ -726,10 +752,9 @@ public: bool _set(const StringName &p_name, const Variant &p_value) { bool update_obj = false; bool change_notify_deserved = false; - for (Map<int, List<float>>::Element *E = key_ofs_map.front(); E; E = E->next()) { - int track = E->key(); - for (float &F : E->value()) { - float key_ofs = F; + for (const KeyValue<int, List<float>> &E : key_ofs_map) { + int track = E.key; + for (const float &key_ofs : E.value) { int key = animation->track_find_key(track, key_ofs, true); ERR_FAIL_COND_V(key == -1, false); @@ -781,19 +806,34 @@ 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_BLEND_SHAPE: case Animation::TYPE_VALUE: { if (name == "value") { Variant value = p_value; @@ -984,10 +1024,9 @@ public: } bool _get(const StringName &p_name, Variant &r_ret) const { - for (Map<int, List<float>>::Element *E = key_ofs_map.front(); E; E = E->next()) { - int track = E->key(); - for (float &F : E->value()) { - float key_ofs = F; + for (const KeyValue<int, List<float>> &E : key_ofs_map) { + int track = E.key; + for (const float &key_ofs : E.value) { int key = animation->track_find_key(track, key_ofs, true); ERR_CONTINUE(key == -1); @@ -1012,13 +1051,16 @@ 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_BLEND_SHAPE: case Animation::TYPE_VALUE: { if (name == "value") { r_ret = animation->track_get_key_value(track, key); @@ -1119,15 +1161,15 @@ public: bool show_time = true; bool same_track_type = true; bool same_key_type = true; - for (Map<int, List<float>>::Element *E = key_ofs_map.front(); E; E = E->next()) { - int track = E->key(); + for (const KeyValue<int, List<float>> &E : key_ofs_map) { + int track = E.key; ERR_FAIL_INDEX(track, animation->get_track_count()); if (first_track < 0) { first_track = track; } - if (show_time && E->value().size() > 1) { + if (show_time && E.value.size() > 1) { show_time = false; } @@ -1137,7 +1179,7 @@ public: same_key_type = false; } - for (float &F : E->value()) { + for (const float &F : E.value) { int key = animation->track_find_key(track, F, true); ERR_FAIL_COND(key == -1); if (first_key < 0) { @@ -1162,11 +1204,18 @@ 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_BLEND_SHAPE: { + p_list->push_back(PropertyInfo(Variant::FLOAT, "value")); + } break; case Animation::TYPE_VALUE: { if (same_key_type) { Variant v = animation->track_get_key_value(first_track, first_key); @@ -1180,7 +1229,7 @@ public: String hint_string; if (v.get_type() == Variant::OBJECT) { - //could actually check the object property if exists..? yes i will! + // Could actually check the object property if exists..? Yes I will! Ref<Resource> res = v; if (res.is_valid()) { hint = PROPERTY_HINT_RESOURCE_TYPE; @@ -1362,7 +1411,10 @@ 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("KeyBlendShape"), SNAME("EditorIcons")), TTR("Blend Shape 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")); @@ -1391,7 +1443,7 @@ void AnimationTimelineEdit::_notification(int p_what) { float l = animation->get_length(); if (l <= 0) { - l = 0.001; //avoid crashor + l = 0.001; // Avoid crashor. } Ref<Texture2D> hsize_icon = get_theme_icon(SNAME("Hsize"), SNAME("EditorIcons")); @@ -1404,18 +1456,12 @@ void AnimationTimelineEdit::_notification(int p_what) { for (int i = 0; i < animation->get_track_count(); i++) { if (animation->track_get_key_count(i) > 0) { float beg = animation->track_get_key_time(i, 0); - /*if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { - beg += animation->bezier_track_get_key_in_handle(i, 0).x; - }* not worth it since they have no use */ if (beg < time_min) { time_min = beg; } float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1); - /*if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { - end += animation->bezier_track_get_key_out_handle(i, animation->track_get_key_count(i) - 1).x; - } not worth it since they have no use */ if (end > time_max) { time_max = end; @@ -1425,8 +1471,6 @@ void AnimationTimelineEdit::_notification(int p_what) { float extra = (zoomw / scale) * 0.5; - //if (time_min < -0.001) - // time_min -= extra; time_max += extra; set_min(time_min); set_max(time_max); @@ -1839,9 +1883,12 @@ 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[9] = { 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("KeyTrackBlendShape"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), @@ -1856,7 +1903,7 @@ void AnimationTrackEdit::_notification(int p_what) { { Ref<Texture2D> check = animation->track_is_enabled(track) ? get_theme_icon(SNAME("checked"), SNAME("CheckBox")) : get_theme_icon(SNAME("unchecked"), SNAME("CheckBox")); - int ofs = in_group ? check->get_width() : 0; //not the best reference for margin but.. + int ofs = in_group ? check->get_width() : 0; // Not the best reference for margin but.. check_rect = Rect2(Point2(ofs, int(get_size().height - check->get_height()) / 2), check->get_size()); draw_texture(check, check_rect.position); @@ -1974,7 +2021,7 @@ void AnimationTrackEdit::_notification(int p_what) { ofs += hsep; { - //callmode + // Callmode. Animation::UpdateMode update_mode; @@ -1993,7 +2040,7 @@ void AnimationTrackEdit::_notification(int p_what) { if (animation->track_get_type(track) == Animation::TYPE_VALUE) { draw_texture(update_icon, update_mode_rect.position); } - //make it easier to click + // Make it easier to click. update_mode_rect.position.y = 0; update_mode_rect.size.y = get_size().height; @@ -2022,7 +2069,7 @@ void AnimationTrackEdit::_notification(int p_what) { } { - //interp + // Interp. Animation::InterpolationType interp_mode = animation->track_get_interpolation_type(track); @@ -2032,17 +2079,17 @@ 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_BLEND_SHAPE || 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 + // Make it easier to click. interp_mode_rect.position.y = 0; interp_mode_rect.size.y = get_size().height; 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_BLEND_SHAPE || 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 { @@ -2055,7 +2102,7 @@ void AnimationTrackEdit::_notification(int p_what) { } { - //loop + // Loop. bool loop_wrap = animation->track_get_interpolation_loop_wrap(track); @@ -2065,7 +2112,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_BLEND_SHAPE || 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); } @@ -2075,7 +2122,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_BLEND_SHAPE || 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 { @@ -2088,7 +2135,7 @@ void AnimationTrackEdit::_notification(int p_what) { } { - //erase + // Erase. Ref<Texture2D> icon = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")); @@ -2135,7 +2182,7 @@ Rect2 AnimationTrackEdit::get_key_rect(int p_index, float p_pixels_sec) { } Rect2 rect = Rect2(-type_icon->get_width() / 2, 0, type_icon->get_width(), get_size().height); - //make it a big easier to click + // Make it a big easier to click. rect.position.x -= rect.size.x * 0.5; rect.size.x *= 2; return rect; @@ -2224,7 +2271,7 @@ void AnimationTrackEdit::draw_key(int p_index, float p_pixels_sec, int p_x, bool draw_texture(icon_to_draw, ofs); } -//helper +// Helper. void AnimationTrackEdit::draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled) { int clip_left = timeline->get_name_limit(); int clip_right = get_size().width - timeline->get_buttons_width(); @@ -2249,7 +2296,7 @@ void AnimationTrackEdit::draw_texture_region_clipped(const Ref<Texture2D> &p_tex int clip_left = timeline->get_name_limit(); int clip_right = get_size().width - timeline->get_buttons_width(); - //clip left and right + // Clip left and right. if (clip_left > p_rect.position.x + p_rect.size.x) { return; } @@ -2295,9 +2342,12 @@ void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animati track = p_track; update(); - Ref<Texture2D> type_icons[6] = { + Ref<Texture2D> type_icons[9] = { 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("KeyBlendShape"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), @@ -2458,7 +2508,7 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { key_distance = distance; } } else { - //first one does it + // First one does it. break; } } @@ -2467,17 +2517,21 @@ 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_BLEND_SHAPE: { + float t = animation->track_get_key_value(track, key_idx); + text += "Blend Shape: " + itos(t) + "\n"; } break; case Animation::TYPE_VALUE: { const Variant &v = animation->track_get_key_value(track, key_idx); @@ -3004,7 +3058,7 @@ AnimationTrackEdit::AnimationTrackEdit() { play_position->set_anchors_and_offsets_preset(PRESET_WIDE); play_position->connect("draw", callable_mp(this, &AnimationTrackEdit::_play_position_draw)); set_focus_mode(FOCUS_CLICK); - set_mouse_filter(MOUSE_FILTER_PASS); //scroll has to work too for selection + set_mouse_filter(MOUSE_FILTER_PASS); // Scroll has to work too for selection. } ////////////////////////////////////// @@ -3332,39 +3386,31 @@ static bool track_type_is_resettable(Animation::TrackType p_type) { switch (p_type) { case Animation::TYPE_VALUE: [[fallthrough]]; + case Animation::TYPE_BLEND_SHAPE: + [[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; } } -void AnimationTrackEditor::_query_insert(const InsertData &p_id) { - if (insert_frame != Engine::get_singleton()->get_frames_drawn()) { - //clear insert list for the frame if frame changed - if (insert_confirm->is_visible()) { - return; //do nothing - } - insert_data.clear(); - insert_query = false; - } - insert_frame = Engine::get_singleton()->get_frames_drawn(); - - for (const InsertData &E : insert_data) { - //prevent insertion of multiple tracks - if (E.path == p_id.path) { - return; //already inserted a track for this on this frame - } - } - - insert_data.push_back(p_id); +void AnimationTrackEditor::make_insert_queue() { + insert_data.clear(); + insert_queue = true; +} +void AnimationTrackEditor::commit_insert_queue() { bool reset_allowed = true; - AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); if (player->has_animation("RESET") && player->get_animation("RESET") == animation) { - // Avoid messing with the reset animation itself + // Avoid messing with the reset animation itself. reset_allowed = false; } else { bool some_resettable = false; @@ -3379,74 +3425,82 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) { } } - if (p_id.track_idx == -1) { - int num_tracks = 0; - bool all_bezier = true; - for (int i = 0; i < insert_data.size(); i++) { - if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) { - all_bezier = false; - } - - if (insert_data[i].track_idx == -1) { - ++num_tracks; - } + // Organize insert data. + int num_tracks = 0; + String last_track_query; + bool all_bezier = true; + for (int i = 0; i < insert_data.size(); i++) { + if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) { + all_bezier = false; + } - if (insert_data[i].type != Animation::TYPE_VALUE) { - continue; - } + if (insert_data[i].track_idx == -1) { + ++num_tracks; + last_track_query = insert_data[i].query; + } - switch (insert_data[i].value.get_type()) { - case Variant::INT: - case Variant::FLOAT: - case Variant::VECTOR2: - case Variant::VECTOR3: - case Variant::QUATERNION: - case Variant::PLANE: - case Variant::COLOR: { - // Valid. - } break; - default: { - all_bezier = false; - } - } + if (insert_data[i].type != Animation::TYPE_VALUE) { + continue; } - if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) { - //potential new key, does not exist - if (num_tracks == 1) { - // TRANSLATORS: %s will be replaced by a phrase describing the target of track. - insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?"), p_id.query)); - } else { - insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?"), num_tracks)); + switch (insert_data[i].value.get_type()) { + case Variant::INT: + case Variant::FLOAT: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::QUATERNION: + case Variant::PLANE: + case Variant::COLOR: { + // Valid. + } break; + default: { + all_bezier = false; } + } + } - insert_confirm_bezier->set_visible(all_bezier); - insert_confirm_reset->set_visible(reset_allowed); - - insert_confirm->get_ok_button()->set_text(TTR("Create")); - insert_confirm->popup_centered(); - insert_query = true; + if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true)) && num_tracks > 0) { + // Potentially a new key, does not exist. + if (num_tracks == 1) { + // TRANSLATORS: %s will be replaced by a phrase describing the target of track. + insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?"), last_track_query)); } else { - call_deferred(SNAME("_insert_delay"), reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), all_bezier && EDITOR_GET("editors/animation/default_create_bezier_tracks")); - insert_queue = true; + insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?"), num_tracks)); } + insert_confirm_bezier->set_visible(all_bezier); + insert_confirm_reset->set_visible(reset_allowed); + + insert_confirm->get_ok_button()->set_text(TTR("Create")); + insert_confirm->popup_centered(); } else { - if (!insert_query && !insert_queue) { - // Create Beziers wouldn't make sense in this case, where no tracks are being created - call_deferred(SNAME("_insert_delay"), reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), false); - insert_queue = true; - } + _insert_track(reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), all_bezier && EDITOR_GET("editors/animation/default_create_bezier_tracks")); } + + insert_queue = false; } -void AnimationTrackEditor::_insert_delay(bool p_create_reset, bool p_create_beziers) { - if (insert_query) { - //discard since it's entered into query mode - insert_queue = false; - return; +void AnimationTrackEditor::_query_insert(const InsertData &p_id) { + if (!insert_queue) { + insert_data.clear(); + } + + for (const InsertData &E : insert_data) { + // Prevent insertion of multiple tracks. + if (E.path == p_id.path) { + return; // Already inserted a track this frame. + } + } + + insert_data.push_back(p_id); + + // Without queue, commit immediately. + if (!insert_queue) { + commit_insert_queue(); } +} +void AnimationTrackEditor::_insert_track(bool p_create_reset, bool p_create_beziers) { undo_redo->create_action(TTR("Anim Insert")); Ref<Animation> reset_anim; @@ -3481,7 +3535,6 @@ void AnimationTrackEditor::_insert_delay(bool p_create_reset, bool p_create_bezi set_anim_pos(pos); emit_signal(SNAME("timeline_changed"), pos, true); } - insert_queue = false; } void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_sub, const Transform3D &p_xform) { @@ -3493,7 +3546,7 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_ } ERR_FAIL_COND(!root); - //let's build a node path + // Let's build a node path. String path = root->get_path_to(p_node); if (p_sub != "") { path += ":" + p_sub; @@ -3501,45 +3554,86 @@ 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 + { + 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) { + if (!keying) { + return false; + } + if (!animation.is_valid()) { + return false; + } + if (!root) { + return false; + } - _query_insert(id); + //let's build a node path + String path = root->get_path_to(p_node); + if (p_sub != "") { + path += ":" + p_sub; + } + int track_id = animation->find_track(path); + if (track_id >= 0) { + 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; + } + } + return false; } void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) { String path = p_path; - //animation property is a special case, always creates an animation track + // Animation property is a special case, always creates an animation track. for (int i = 0; i < animation->get_track_count(); i++) { String np = animation->track_get_path(i); if (path == np && animation->track_get_type(i) == Animation::TYPE_ANIMATION) { - //exists + // Exists. InsertData id; id.path = path; id.track_idx = i; @@ -3548,7 +3642,7 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant // TRANSLATORS: This describes the target of new animation track, will be inserted into another string. id.query = TTR("animation"); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); return; } @@ -3561,20 +3655,20 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant id.type = Animation::TYPE_ANIMATION; id.query = TTR("animation"); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); } void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) { ERR_FAIL_COND(!root); - //let's build a node path + // Let's build a node path. Node *node = p_node; String path = root->get_path_to(node); if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { - if (node == AnimationPlayerEditor::singleton->get_player()) { + if (node == AnimationPlayerEditor::get_singleton()->get_player()) { EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); return; } @@ -3593,7 +3687,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p NodePath np = path; - //locate track + // Locate track. bool inserted = false; @@ -3611,14 +3705,14 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p // TRANSLATORS: This describes the target of new animation track, will be inserted into another string. id.query = vformat(TTR("property '%s'"), p_property); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); inserted = true; } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { Variant value; String track_path = animation->track_get_path(i); if (track_path == np) { - value = p_value; //all good + value = p_value; // All good. } else { int sep = track_path.rfind(":"); if (sep != -1) { @@ -3641,7 +3735,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p id.type = Animation::TYPE_BEZIER; id.query = vformat(TTR("property '%s'"), p_property); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); inserted = true; } @@ -3657,7 +3751,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p id.type = Animation::TYPE_VALUE; id.query = vformat(TTR("property '%s'"), p_property); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); } @@ -3665,7 +3759,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari EditorHistory *history = EditorNode::get_singleton()->get_editor_history(); ERR_FAIL_COND(!root); - //let's build a node path + // Let's build a node path. ERR_FAIL_COND(history->get_path_size() == 0); Object *obj = ObjectDB::get_instance(history->get_path_object(0)); ERR_FAIL_COND(!Object::cast_to<Node>(obj)); @@ -3675,7 +3769,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari String path = root->get_path_to(node); if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { - if (node == AnimationPlayerEditor::singleton->get_player()) { + if (node == AnimationPlayerEditor::get_singleton()->get_player()) { EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); return; } @@ -3693,10 +3787,11 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari NodePath np = path; - //locate track + // Locate track. bool inserted = false; + make_insert_queue(); for (int i = 0; i < animation->get_track_count(); i++) { if (animation->track_get_type(i) == Animation::TYPE_VALUE) { if (animation->track_get_path(i) != np) { @@ -3710,13 +3805,13 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari id.type = Animation::TYPE_VALUE; id.query = vformat(TTR("property '%s'"), p_property); id.advance = p_advance; - //dialog insert + // Dialog insert. _query_insert(id); inserted = true; } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { Variant value; if (animation->track_get_path(i) == np) { - value = p_value; //all good + value = p_value; // All good. } else { String tpath = animation->track_get_path(i); int index = tpath.rfind(":"); @@ -3735,11 +3830,12 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari id.type = Animation::TYPE_BEZIER; id.query = vformat(TTR("property '%s'"), p_property); id.advance = p_advance; - //dialog insert + // Dialog insert. _query_insert(id); inserted = true; } } + commit_insert_queue(); if (!inserted) { InsertData id; @@ -3749,13 +3845,13 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari id.type = Animation::TYPE_VALUE; id.query = vformat(TTR("property '%s'"), p_property); id.advance = p_advance; - //dialog insert + // Dialog insert. _query_insert(id); } } Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() { - AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); if (player->has_animation("RESET")) { return player->get_animation("RESET"); } else { @@ -3763,9 +3859,9 @@ Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() { reset_anim.instantiate(); reset_anim->set_length(ANIM_MIN_LENGTH); undo_redo->add_do_method(player, "add_animation", "RESET", reset_anim); - undo_redo->add_do_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player); + undo_redo->add_do_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player); undo_redo->add_undo_method(player, "remove_animation", "RESET"); - undo_redo->add_undo_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player); + undo_redo->add_undo_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player); return reset_anim; } } @@ -3932,7 +4028,7 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD animation->add_track(p_id.type); animation->track_set_path(animation->get_track_count() - 1, p_id.path); PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); - animation->remove_track(animation->get_track_count() - 1); //hack + animation->remove_track(animation->get_track_count() - 1); // Hack. if (h.type == Variant::FLOAT || h.type == Variant::VECTOR2 || @@ -3969,18 +4065,14 @@ 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_BLEND_SHAPE: 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); @@ -4081,7 +4173,7 @@ void AnimationTrackEditor::_update_tracks() { for (int i = 0; i < animation->get_track_count(); i++) { AnimationTrackEdit *track_edit = nullptr; - //find hint and info for plugin + // Find hint and info for plugin. if (use_filter) { NodePath path = animation->track_get_path(i); @@ -4089,10 +4181,10 @@ void AnimationTrackEditor::_update_tracks() { if (root && root->has_node(path)) { Node *node = root->get_node(path); if (!node) { - continue; // no node, no filter + continue; // No node, no filter. } if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { - continue; //skip track due to not selected + continue; // Skip track due to not selected. } } } @@ -4154,7 +4246,7 @@ void AnimationTrackEditor::_update_tracks() { } if (track_edit == nullptr) { - //no valid plugin_found + // No valid plugin_found. track_edit = memnew(AnimationTrackEdit); } @@ -4229,11 +4321,11 @@ void AnimationTrackEditor::_update_tracks() { void AnimationTrackEditor::_animation_changed() { if (animation_changing_awaiting_update) { - return; //all will be updated, don't bother with anything + return; // All will be updated, don't bother with anything. } if (key_edit && key_edit->setting) { - //if editing a key, just update the edited track, makes refresh less costly + // If editing a key, just update the edited track, makes refresh less costly. if (key_edit->track < track_edits.size()) { if (animation->track_get_type(key_edit->track) == Animation::TYPE_BEZIER) { bezier_edit->update(); @@ -4287,7 +4379,7 @@ void AnimationTrackEditor::_animation_update() { } if (track_edits.size() == animation->get_track_count()) { - //check tracks are the same + // Check tracks are the same. for (int i = 0; i < track_edits.size(); i++) { if (track_edits[i]->get_path() != animation->track_get_path(i)) { @@ -4396,8 +4488,13 @@ 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_BLEND_SHAPE && !node->is_class("MeshInstance3D")) { + EditorNode::get_singleton()->show_warning(TTR("Blend Shape tracks only apply to MeshInstance3D nodes.")); + return; + } + + 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; } @@ -4407,7 +4504,16 @@ 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_BLEND_SHAPE: { + adding_track_path = path_to; + Vector<Variant::Type> filter; + filter.push_back(Variant::FLOAT); + prop_selector->set_type_filter(filter); + prop_selector->select_property_from_instance(node); + } break; + 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); @@ -4449,7 +4555,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { return; } - if (node == AnimationPlayerEditor::singleton->get_player()) { + if (node == AnimationPlayerEditor::get_singleton()->get_player()) { EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); return; } @@ -4481,12 +4587,12 @@ void AnimationTrackEditor::_new_track_property_selected(String p_name) { if (adding_track_type == Animation::TYPE_VALUE) { Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE; { - //hack + // Hack. NodePath np; animation->add_track(Animation::TYPE_VALUE); animation->track_set_path(animation->get_track_count() - 1, full_path); PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); - animation->remove_track(animation->get_track_count() - 1); //hack + animation->remove_track(animation->get_track_count() - 1); // Hack. if (h.type == Variant::FLOAT || h.type == Variant::VECTOR2 || h.type == Variant::RECT2 || @@ -4514,12 +4620,12 @@ void AnimationTrackEditor::_new_track_property_selected(String p_name) { } else { Vector<String> subindices; { - //hack + // Hack. NodePath np; animation->add_track(Animation::TYPE_VALUE); animation->track_set_path(animation->get_track_count() - 1, full_path); PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); - animation->remove_track(animation->get_track_count() - 1); //hack + animation->remove_track(animation->get_track_count() - 1); // Hack. bool valid; subindices = _get_bezier_subindices_for_type(h.type, &valid); if (!valid) { @@ -4571,12 +4677,12 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { if (snap->is_pressed() && step->get_value() != 0) { p_ofs = snap_time(p_ofs); } - while (animation->track_find_key(p_track, p_ofs, true) != -1) { //make sure insertion point is valid + while (animation->track_find_key(p_track, p_ofs, true) != -1) { // Make sure insertion point is valid. p_ofs += 0.001; } 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; @@ -4588,18 +4694,55 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { return; } - Transform3D xf = base->get_transform(); + 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(); - Vector3 loc = xf.get_origin(); - Vector3 scale = xf.basis.get_scale_local(); - Quaternion rot = xf.basis; + } 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; + } + Node3D *base = Object::cast_to<Node3D>(root->get_node(animation->track_get_path(p_track))); - 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); + if (!base) { + EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key")); + return; + } + + 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 scale = base->get_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(); + + } break; + case Animation::TYPE_BLEND_SHAPE: case Animation::TYPE_VALUE: { NodePath bp; Variant value; @@ -4766,16 +4909,16 @@ struct _AnimMoveRestore { Variant key; float transition = 0; }; -//used for undo/redo +// Used for undo/redo. void AnimationTrackEditor::_clear_key_edit() { if (key_edit) { - //if key edit is the object being inspected, remove it first + // If key edit is the object being inspected, remove it first. if (EditorNode::get_singleton()->get_inspector()->get_edited_object() == key_edit) { EditorNode::get_singleton()->push_item(nullptr); } - //then actually delete it + // Then actually delete it. memdelete(key_edit); key_edit = nullptr; } @@ -4831,8 +4974,8 @@ void AnimationTrackEditor::_update_key_edit() { Map<int, List<float>> key_ofs_map; Map<int, NodePath> base_map; int first_track = -1; - for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { - int track = E->key().track; + for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { + int track = E.key.track; if (first_track < 0) { first_track = track; } @@ -4842,7 +4985,7 @@ void AnimationTrackEditor::_update_key_edit() { base_map[track] = NodePath(); } - key_ofs_map[track].push_back(animation->track_get_key_time(track, E->key().key)); + key_ofs_map[track].push_back(animation->track_get_key_time(track, E.key.key)); } multi_key_edit->key_ofs_map = key_ofs_map; multi_key_edit->base_map = base_map; @@ -4889,11 +5032,11 @@ void AnimationTrackEditor::_move_selection_commit() { List<_AnimMoveRestore> to_restore; float motion = moving_selection_offset; - // 1 - remove the keys + // 1 - remove the keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); } - // 2 - remove overlapped keys + // 2 - Remove overlapped keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newtime = snap_time(E->get().pos + motion); int idx = animation->track_find_key(E->key().track, newtime, true); @@ -4904,7 +5047,7 @@ void AnimationTrackEditor::_move_selection_commit() { sk.key = idx; sk.track = E->key().track; if (selection.has(sk)) { - continue; //already in selection, don't save + continue; // Already in selection, don't save. } undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime); @@ -4918,24 +5061,24 @@ void AnimationTrackEditor::_move_selection_commit() { to_restore.push_back(amr); } - // 3 - move the keys (re insert them) + // 3 - Move the keys (Reinsert them). for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = snap_time(E->get().pos + motion); undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } - // 4 - (undo) remove inserted keys + // 4 - (Undo) Remove inserted keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = snap_time(E->get().pos + motion); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos); } - // 5 - (undo) reinsert keys + // 5 - (Undo) Reinsert keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } - // 6 - (undo) reinsert overlapped keys + // 6 - (Undo) Reinsert overlapped keys. for (_AnimMoveRestore &amr : to_restore) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); } @@ -4943,7 +5086,7 @@ void AnimationTrackEditor::_move_selection_commit() { undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); - // 7 - reselect + // 7 - Reselect. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float oldpos = E->get().pos; float newpos = snap_time(oldpos + motion); @@ -5013,18 +5156,18 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { box_select_rect = Rect2(); } else if (box_selecting) { if (box_selection->is_visible_in_tree()) { - //only if moved + // Only if moved. for (int i = 0; i < track_edits.size(); i++) { Rect2 local_rect = box_select_rect; local_rect.position -= track_edits[i]->get_global_position(); track_edits[i]->append_to_selection(local_rect, mb->is_command_pressed()); } - if (_get_track_selected() == -1 && track_edits.size() > 0) { //minimal hack to make shortcuts work + if (_get_track_selected() == -1 && track_edits.size() > 0) { // Minimal hack to make shortcuts work. track_edits[track_edits.size() - 1]->grab_focus(); } } else { - _clear_selection(); //clear it + _clear_selection(); // Clear it. } box_selection->hide(); @@ -5040,7 +5183,7 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { if (mm.is_valid() && box_selecting) { if (!(mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT)) { - //no longer + // No longer. box_selection->hide(); box_selecting = false; return; @@ -5080,16 +5223,16 @@ void AnimationTrackEditor::_cancel_bezier_edit() { } void AnimationTrackEditor::_bezier_edit(int p_for_track) { - _clear_selection(); //bezier probably wants to use a separate selection mode + _clear_selection(); // Bezier probably wants to use a separate selection mode. bezier_edit->set_root(root); bezier_edit->set_animation_and_track(animation, p_for_track); scroll->hide(); bezier_edit->show(); - //search everything within the track and curve- edit it + // Search everything within the track and curve - edit it. } void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { - //duplicait! + // Duplicait! if (selection.size() && animation.is_valid() && (!transpose || (_get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count()))) { int top_track = 0x7FFFFFFF; float top_time = 1e10; @@ -5147,7 +5290,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { undo_redo->commit_action(); - //reselect duplicated + // Reselect duplicated. Map<SelectedKey, KeyInfo> new_selection; for (const Pair<int, float> &E : new_selection_values) { @@ -5176,7 +5319,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { } void AnimationTrackEditor::_edit_menu_about_to_popup() { - AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset()); } @@ -5259,7 +5402,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { text += sn[j]; } - path = NodePath(node->get_path().get_names(), path.get_subnames(), true); //store full path instead for copying + path = NodePath(node->get_path().get_names(), path.get_subnames(), true); // Store full path instead for copying. } else { text = path; int sep = text.find(":"); @@ -5269,8 +5412,17 @@ 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_BLEND_SHAPE: + text += " (BlendShape)"; break; case Animation::TYPE_METHOD: text += " (Methods)"; @@ -5386,8 +5538,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { float len = -1e20; float pivot = 0; - for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { - float t = animation->track_get_key_time(E->key().track, E->key().key); + for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { + float t = animation->track_get_key_time(E.key.track, E.key.key); if (t < from_t) { from_t = t; } @@ -5413,11 +5565,11 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { List<_AnimMoveRestore> to_restore; - // 1-remove the keys + // 1 - Remove the keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); } - // 2- remove overlapped keys + // 2 - Remove overlapped keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newtime = (E->get().pos - from_t) * s + from_t; int idx = animation->track_find_key(E->key().track, newtime, true); @@ -5428,7 +5580,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { sk.key = idx; sk.track = E->key().track; if (selection.has(sk)) { - continue; //already in selection, don't save + continue; // Already in selection, don't save. } undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime); @@ -5443,24 +5595,24 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } #define _NEW_POS(m_ofs) (((s > 0) ? m_ofs : from_t + (len - (m_ofs - from_t))) - pivot) * ABS(s) + from_t - // 3-move the keys (re insert them) + // 3 - Move the keys (re insert them). for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = _NEW_POS(E->get().pos); undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } - // 4-(undo) remove inserted keys + // 4 - (Undo) Remove inserted keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = _NEW_POS(E->get().pos); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos); } - // 5-(undo) reinsert keys + // 5 - (Undo) Reinsert keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } - // 6-(undo) reinsert overlapped keys + // 6 - (Undo) Reinsert overlapped keys. for (_AnimMoveRestore &amr : to_restore) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); } @@ -5468,7 +5620,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); - // 7-reselect + // 7-reselect. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float oldpos = E->get().pos; float newpos = _NEW_POS(oldpos); @@ -5520,7 +5672,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { goto_prev_step(false); } break; case EDIT_APPLY_RESET: { - AnimationPlayerEditor::singleton->get_player()->apply_reset(true); + AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true); } break; case EDIT_OPTIMIZE_ANIMATION: { @@ -5540,9 +5692,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { case EDIT_CLEAN_UP_ANIMATION_CONFIRM: { if (cleanup_all->is_pressed()) { List<StringName> names; - AnimationPlayerEditor::singleton->get_player()->get_animation_list(&names); + AnimationPlayerEditor::get_singleton()->get_player()->get_animation_list(&names); for (const StringName &E : names) { - _cleanup_animation(AnimationPlayerEditor::singleton->get_player()->get_animation(E)); + _cleanup_animation(AnimationPlayerEditor::get_singleton()->get_player()->get_animation(E)); } } else { _cleanup_animation(animation); @@ -5617,7 +5769,7 @@ bool AnimationTrackEditor::is_grouping_tracks() { void AnimationTrackEditor::_selection_changed() { if (selected_filter->is_pressed()) { - _update_tracks(); //needs updatin + _update_tracks(); // Needs updatin. } else { for (int i = 0; i < track_edits.size(); i++) { track_edits[i]->update(); @@ -5686,7 +5838,6 @@ void AnimationTrackEditor::_bind_methods() { ClassDB::bind_method("_animation_update", &AnimationTrackEditor::_animation_update); ClassDB::bind_method("_track_grab_focus", &AnimationTrackEditor::_track_grab_focus); ClassDB::bind_method("_update_tracks", &AnimationTrackEditor::_update_tracks); - ClassDB::bind_method("_insert_delay", &AnimationTrackEditor::_insert_delay); ClassDB::bind_method("_clear_selection_for_anim", &AnimationTrackEditor::_clear_selection_for_anim); ClassDB::bind_method("_select_at_anim", &AnimationTrackEditor::_select_at_anim); @@ -5772,7 +5923,7 @@ AnimationTrackEditor::AnimationTrackEditor() { undo_redo = EditorNode::get_singleton()->get_undo_redo(); main_panel = memnew(PanelContainer); - main_panel->set_focus_mode(FOCUS_ALL); // allow panel to have focus so that shortcuts work as expected. + main_panel->set_focus_mode(FOCUS_ALL); // Allow panel to have focus so that shortcuts work as expected. add_child(main_panel); main_panel->set_v_size_flags(SIZE_EXPAND_FILL); HBoxContainer *timeline_scroll = memnew(HBoxContainer); @@ -5808,7 +5959,7 @@ AnimationTrackEditor::AnimationTrackEditor() { scroll->set_v_size_flags(SIZE_EXPAND_FILL); VScrollBar *sb = scroll->get_v_scrollbar(); scroll->remove_child(sb); - timeline_scroll->add_child(sb); //move here so timeline and tracks are always aligned + timeline_scroll->add_child(sb); // Move here so timeline and tracks are always aligned. scroll->connect("gui_input", callable_mp(this, &AnimationTrackEditor::_scroll_input)); bezier_edit = memnew(AnimationBezierTrackEdit); @@ -5849,7 +6000,7 @@ AnimationTrackEditor::AnimationTrackEditor() { selected_filter = memnew(Button); selected_filter->set_flat(true); - selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); //same function works the same + selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same. selected_filter->set_toggle_mode(true); selected_filter->set_tooltip(TTR("Only show tracks from nodes selected in tree.")); @@ -5950,9 +6101,6 @@ AnimationTrackEditor::AnimationTrackEditor() { add_child(method_selector); method_selector->connect("selected", callable_mp(this, &AnimationTrackEditor::_add_method_key)); - inserting = false; - insert_query = false; - insert_frame = 0; insert_queue = false; insert_confirm = memnew(ConfirmationDialog); @@ -5985,13 +6133,13 @@ AnimationTrackEditor::AnimationTrackEditor() { box_selection->connect("draw", callable_mp(this, &AnimationTrackEditor::_box_selection_draw)); box_selecting = false; - //default plugins + // Default Plugins. Ref<AnimationTrackEditDefaultPlugin> def_plugin; def_plugin.instantiate(); add_track_edit_plugin(def_plugin); - //dialogs + // Dialogs. optimize_dialog = memnew(ConfirmationDialog); add_child(optimize_dialog); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 4da708dd1c..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" @@ -352,10 +352,7 @@ class AnimationTrackEditor : public VBoxContainer { CheckBox *insert_confirm_reset; ConfirmationDialog *insert_confirm; bool insert_queue; - bool inserting; - bool insert_query; List<InsertData> insert_data; - uint64_t insert_frame; void _query_insert(const InsertData &p_id); Ref<Animation> _create_and_get_reset_animation(); @@ -370,7 +367,7 @@ class AnimationTrackEditor : public VBoxContainer { } }; TrackIndices _confirm_insert(InsertData p_id, TrackIndices p_next_tracks, bool p_create_reset, Ref<Animation> p_reset_anim, bool p_create_beziers); - void _insert_delay(bool p_create_reset, bool p_create_beziers); + void _insert_track(bool p_create_reset, bool p_create_beziers); void _root_removed(Node *p_root); @@ -531,6 +528,9 @@ public: void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false); void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance); void insert_transform_key(Node3D *p_node, const String &p_sub, const Transform3D &p_xform); + bool has_transform_track(Node3D *p_node, const String &p_sub); + void make_insert_queue(); + void commit_insert_queue(); void show_select_node_warning(bool p_show); diff --git a/editor/audio_stream_preview.cpp b/editor/audio_stream_preview.cpp index f7f4988873..2efcdcda31 100644 --- a/editor/audio_stream_preview.cpp +++ b/editor/audio_stream_preview.cpp @@ -216,15 +216,15 @@ AudioStreamPreviewGenerator *AudioStreamPreviewGenerator::singleton = nullptr; void AudioStreamPreviewGenerator::_notification(int p_what) { if (p_what == NOTIFICATION_PROCESS) { List<ObjectID> to_erase; - for (Map<ObjectID, Preview>::Element *E = previews.front(); E; E = E->next()) { - if (!E->get().generating.is_set()) { - if (E->get().thread) { - E->get().thread->wait_to_finish(); - memdelete(E->get().thread); - E->get().thread = nullptr; + for (KeyValue<ObjectID, Preview> &E : previews) { + if (!E.value.generating.is_set()) { + if (E.value.thread) { + E.value.thread->wait_to_finish(); + memdelete(E.value.thread); + E.value.thread = nullptr; } - if (!ObjectDB::get_instance(E->key())) { //no longer in use, get rid of preview - to_erase.push_back(E->key()); + if (!ObjectDB::get_instance(E.key)) { //no longer in use, get rid of preview + to_erase.push_back(E.key); } } } diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp index fbd3aaa409..485d58f4a3 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp @@ -306,8 +306,8 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const Array arr; DebugAdapterProtocol *dap = DebugAdapterProtocol::get_singleton(); - for (Map<DAP::StackFrame, List<int>>::Element *E = dap->stackframe_list.front(); E; E = E->next()) { - DAP::StackFrame sf = E->key(); + for (const KeyValue<DAP::StackFrame, List<int>> &E : dap->stackframe_list) { + DAP::StackFrame sf = E.key; if (!lines_at_one) { sf.line--; } diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index a1eb71235c..e53f66e72e 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -200,12 +200,12 @@ ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) { } void EditorDebuggerInspector::clear_cache() { - for (Map<ObjectID, EditorDebuggerRemoteObject *>::Element *E = remote_objects.front(); E; E = E->next()) { + for (const KeyValue<ObjectID, EditorDebuggerRemoteObject *> &E : remote_objects) { EditorNode *editor = EditorNode::get_singleton(); - if (editor->get_editor_history()->get_current() == E->value()->get_instance_id()) { + if (editor->get_editor_history()->get_current() == E.value->get_instance_id()) { editor->push_item(nullptr); } - memdelete(E->value()); + memdelete(E.value); } remote_objects.clear(); remote_dependencies.clear(); diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index be84e8dec5..188f5708aa 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -328,9 +328,9 @@ void EditorDebuggerNode::_notification(int p_what) { debugger->set_editor_remote_tree(remote_scene_tree); debugger->start(server->take_connection()); // Send breakpoints. - for (Map<Breakpoint, bool>::Element *E = breakpoints.front(); E; E = E->next()) { - const Breakpoint &bp = E->key(); - debugger->set_breakpoint(bp.source, bp.line, E->get()); + for (const KeyValue<Breakpoint, bool> &E : breakpoints) { + const Breakpoint &bp = E.key; + debugger->set_breakpoint(bp.source, bp.line, E.value); } // Will arrive too late, how does the regular run work? debugger->update_live_edit_root(); @@ -497,8 +497,8 @@ void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) { set_breakpoint(p_path, p_lines[i], true); } - for (Map<Breakpoint, bool>::Element *E = breakpoints.front(); E; E = E->next()) { - Breakpoint b = E->key(); + for (const KeyValue<Breakpoint, bool> &E : breakpoints) { + Breakpoint b = E.key; if (b.source == p_path && !p_lines.has(b.line)) { set_breakpoint(p_path, b.line, false); } diff --git a/editor/debugger/editor_network_profiler.cpp b/editor/debugger/editor_network_profiler.cpp index 9479fbd5d4..d4385630be 100644 --- a/editor/debugger/editor_network_profiler.cpp +++ b/editor/debugger/editor_network_profiler.cpp @@ -56,18 +56,18 @@ void EditorNetworkProfiler::_update_frame() { TreeItem *root = counters_display->create_item(); - for (Map<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo>::Element *E = nodes_data.front(); E; E = E->next()) { + for (const KeyValue<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> &E : nodes_data) { TreeItem *node = counters_display->create_item(root); for (int j = 0; j < counters_display->get_columns(); ++j) { node->set_text_align(j, j > 0 ? TreeItem::ALIGN_RIGHT : TreeItem::ALIGN_LEFT); } - node->set_text(0, E->get().node_path); - node->set_text(1, E->get().incoming_rpc == 0 ? "-" : itos(E->get().incoming_rpc)); - node->set_text(2, E->get().incoming_rset == 0 ? "-" : itos(E->get().incoming_rset)); - node->set_text(3, E->get().outgoing_rpc == 0 ? "-" : itos(E->get().outgoing_rpc)); - node->set_text(4, E->get().outgoing_rset == 0 ? "-" : itos(E->get().outgoing_rset)); + node->set_text(0, E.value.node_path); + node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc)); + node->set_text(2, E.value.incoming_rset == 0 ? "-" : itos(E.value.incoming_rset)); + node->set_text(3, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc)); + node->set_text(4, E.value.outgoing_rset == 0 ? "-" : itos(E.value.outgoing_rset)); } } diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index fa9c9f61f5..2fe7cd7886 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -515,11 +515,11 @@ Vector<Vector<String>> EditorProfiler::get_data_as_csv() const { if (!m.valid) { continue; } - for (Map<StringName, Metric::Category *>::Element *E = m.category_ptrs.front(); E; E = E->next()) { - possible_signatures.insert(E->key()); + for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) { + possible_signatures.insert(E.key); } - for (Map<StringName, Metric::Category::Item *>::Element *E = m.item_ptrs.front(); E; E = E->next()) { - possible_signatures.insert(E->key()); + for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) { + possible_signatures.insert(E.key); } } @@ -557,11 +557,11 @@ Vector<Vector<String>> EditorProfiler::get_data_as_csv() const { values.clear(); values.resize(possible_signatures.size()); - for (Map<StringName, Metric::Category *>::Element *E = m.category_ptrs.front(); E; E = E->next()) { - values.write[sig_map[E->key()]] = String::num_real(E->value()->total_time); + for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) { + values.write[sig_map[E.key]] = String::num_real(E.value->total_time); } - for (Map<StringName, Metric::Category::Item *>::Element *E = m.item_ptrs.front(); E; E = E->next()) { - values.write[sig_map[E->key()]] = String::num_real(E->value()->total); + for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) { + values.write[sig_map[E.key]] = String::num_real(E.value->total); } res.push_back(values); 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/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 5fda1c76ef..a312c161a8 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -147,7 +147,7 @@ void ScriptEditorDebugger::update_tabs() { } void ScriptEditorDebugger::clear_style() { - tabs->add_theme_style_override("panel", nullptr); + tabs->remove_theme_style_override("panel"); } void ScriptEditorDebugger::save_node(ObjectID p_id, const String &p_file) { @@ -501,6 +501,10 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da error->set_text(0, time); error->set_text_align(0, TreeItem::ALIGN_LEFT); + const Color color = get_theme_color(oe.warning ? SNAME("warning_color") : SNAME("error_color"), SNAME("Editor")); + error->set_custom_color(0, color); + error->set_custom_color(1, color); + String error_title; if (oe.callstack.size() > 0) { // If available, use the script's stack in the error title. @@ -1234,7 +1238,7 @@ void ScriptEditorDebugger::update_live_edit_root() { Array msg; msg.push_back(np); if (editor->get_edited_scene()) { - msg.push_back(editor->get_edited_scene()->get_filename()); + msg.push_back(editor->get_edited_scene()->get_scene_file_path()); } else { msg.push_back(""); } diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index a9d8cb219c..d07d77c112 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -74,16 +74,16 @@ void DependencyEditor::_fix_and_find(EditorFileSystemDirectory *efsd, Map<String String path = efsd->get_file_path(i); - for (Map<String, String>::Element *E = candidates[file].front(); E; E = E->next()) { - if (E->get() == String()) { - E->get() = path; + for (KeyValue<String, String> &E : candidates[file]) { + if (E.value == String()) { + E.value = path; continue; } //must match the best, using subdirs - String existing = E->get().replace_first("res://", ""); + String existing = E.value.replace_first("res://", ""); String current = path.replace_first("res://", ""); - String lost = E->key().replace_first("res://", ""); + String lost = E.key.replace_first("res://", ""); Vector<String> existingv = existing.split("/"); existingv.reverse(); @@ -107,7 +107,7 @@ void DependencyEditor::_fix_and_find(EditorFileSystemDirectory *efsd, Map<String if (current_score > existing_score) { //if it was the same, could track distance to new path but.. - E->get() = path; //replace by more accurate + E.value = path; //replace by more accurate } } } @@ -133,10 +133,10 @@ void DependencyEditor::_fix_all() { Map<String, String> remaps; - for (Map<String, Map<String, String>>::Element *E = candidates.front(); E; E = E->next()) { - for (Map<String, String>::Element *F = E->get().front(); F; F = F->next()) { - if (F->get() != String()) { - remaps[F->key()] = F->get(); + for (KeyValue<String, Map<String, String>> &E : candidates) { + for (const KeyValue<String, String> &F : E.value) { + if (F.value != String()) { + remaps[F.key] = F.value; } } } diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index ec162231e9..beead74c53 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -44,8 +44,8 @@ #include "modules/modules_enabled.gen.h" void DocTools::merge_from(const DocTools &p_data) { - for (Map<String, DocData::ClassDoc>::Element *E = class_list.front(); E; E = E->next()) { - DocData::ClassDoc &c = E->get(); + for (KeyValue<String, DocData::ClassDoc> &E : class_list) { + DocData::ClassDoc &c = E.value; if (!p_data.class_list.has(c.name)) { continue; @@ -185,9 +185,9 @@ void DocTools::merge_from(const DocTools &p_data) { } void DocTools::remove_from(const DocTools &p_data) { - for (Map<String, DocData::ClassDoc>::Element *E = p_data.class_list.front(); E; E = E->next()) { - if (class_list.has(E->key())) { - class_list.erase(E->key()); + for (const KeyValue<String, DocData::ClassDoc> &E : p_data.class_list) { + if (class_list.has(E.key)) { + class_list.erase(E.key); } } } @@ -1227,8 +1227,8 @@ static void _write_method_doc(FileAccess *f, const String &p_name, Vector<DocDat } Error DocTools::save_classes(const String &p_default_path, const Map<String, String> &p_class_path) { - for (Map<String, DocData::ClassDoc>::Element *E = class_list.front(); E; E = E->next()) { - DocData::ClassDoc &c = E->get(); + for (KeyValue<String, DocData::ClassDoc> &E : class_list) { + DocData::ClassDoc &c = E.value; String save_path; if (p_class_path.has(c.name)) { diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index fcf79a80a7..0840c3b6a8 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -473,8 +473,8 @@ void EditorAutoloadSettings::update_autoload() { } // Remove deleted/changed autoloads - for (Map<String, AutoLoadInfo>::Element *E = to_remove.front(); E; E = E->next()) { - AutoLoadInfo &info = E->get(); + for (KeyValue<String, AutoLoadInfo> &E : to_remove) { + AutoLoadInfo &info = E.value; if (info.is_singleton) { for (int i = 0; i < ScriptServer::get_language_count(); i++) { ScriptServer::get_language(i)->remove_named_global_constant(info.name); diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 5e50835ef2..aee9c21007 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -554,7 +554,7 @@ void EditorData::remove_scene(int p_idx) { ERR_FAIL_INDEX(p_idx, edited_scene.size()); if (edited_scene[p_idx].root) { for (int i = 0; i < editor_plugins.size(); i++) { - editor_plugins[i]->notify_scene_closed(edited_scene[p_idx].root->get_filename()); + editor_plugins[i]->notify_scene_closed(edited_scene[p_idx].root->get_scene_file_path()); } memdelete(edited_scene[p_idx].root); @@ -583,7 +583,7 @@ bool EditorData::_find_updated_instances(Node *p_root, Node *p_node, Set<String> if (p_node == p_root) { ss = p_node->get_scene_inherited_state(); - } else if (p_node->get_filename() != String()) { + } else if (p_node->get_scene_file_path() != String()) { ss = p_node->get_scene_instance_state(); } @@ -643,12 +643,12 @@ bool EditorData::check_and_update_scene(int p_idx) { } } - new_scene->set_filename(edited_scene[p_idx].root->get_filename()); + new_scene->set_scene_file_path(edited_scene[p_idx].root->get_scene_file_path()); memdelete(edited_scene[p_idx].root); edited_scene.write[p_idx].root = new_scene; - if (new_scene->get_filename() != "") { - edited_scene.write[p_idx].path = new_scene->get_filename(); + if (new_scene->get_scene_file_path() != "") { + edited_scene.write[p_idx].path = new_scene->get_scene_file_path(); } edited_scene.write[p_idx].selection = new_selection; @@ -682,10 +682,10 @@ void EditorData::set_edited_scene_root(Node *p_root) { ERR_FAIL_INDEX(current_edited_scene, edited_scene.size()); edited_scene.write[current_edited_scene].root = p_root; if (p_root) { - if (p_root->get_filename() != "") { - edited_scene.write[current_edited_scene].path = p_root->get_filename(); + if (p_root->get_scene_file_path() != "") { + edited_scene.write[current_edited_scene].path = p_root->get_scene_file_path(); } else { - p_root->set_filename(edited_scene[current_edited_scene].path); + p_root->set_scene_file_path(edited_scene[current_edited_scene].path); } } @@ -764,7 +764,7 @@ Ref<Script> EditorData::get_scene_root_script(int p_idx) const { Ref<Script> s = edited_scene[p_idx].root->get_script(); if (!s.is_valid() && edited_scene[p_idx].root->get_child_count()) { Node *n = edited_scene[p_idx].root->get_child(0); - while (!s.is_valid() && n && n->get_filename() == String()) { + while (!s.is_valid() && n && n->get_scene_file_path() == String()) { s = n->get_script(); n = n->get_parent(); } @@ -777,11 +777,11 @@ String EditorData::get_scene_title(int p_idx, bool p_always_strip_extension) con if (!edited_scene[p_idx].root) { return TTR("[empty]"); } - if (edited_scene[p_idx].root->get_filename() == "") { + if (edited_scene[p_idx].root->get_scene_file_path() == "") { return TTR("[unsaved]"); } - const String filename = edited_scene[p_idx].root->get_filename().get_file(); + const String filename = edited_scene[p_idx].root->get_scene_file_path().get_file(); const String basename = filename.get_basename(); if (p_always_strip_extension) { @@ -795,7 +795,7 @@ String EditorData::get_scene_title(int p_idx, bool p_always_strip_extension) con continue; } - if (edited_scene[i].root && basename == edited_scene[i].root->get_filename().get_file().get_basename()) { + if (edited_scene[i].root && basename == edited_scene[i].root->get_scene_file_path().get_file().get_basename()) { return filename; } } @@ -811,17 +811,17 @@ void EditorData::set_scene_path(int p_idx, const String &p_path) { if (!edited_scene[p_idx].root) { return; } - edited_scene[p_idx].root->set_filename(p_path); + edited_scene[p_idx].root->set_scene_file_path(p_path); } String EditorData::get_scene_path(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, edited_scene.size(), String()); if (edited_scene[p_idx].root) { - if (edited_scene[p_idx].root->get_filename() == "") { - edited_scene[p_idx].root->set_filename(edited_scene[p_idx].path); + if (edited_scene[p_idx].root->get_scene_file_path() == "") { + edited_scene[p_idx].root->set_scene_file_path(edited_scene[p_idx].path); } else { - return edited_scene[p_idx].root->get_filename(); + return edited_scene[p_idx].root->get_scene_file_path(); } } @@ -1105,8 +1105,8 @@ Array EditorSelection::_get_transformable_selected_nodes() { TypedArray<Node> EditorSelection::get_selected_nodes() { TypedArray<Node> ret; - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - ret.push_back(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + ret.push_back(E.key); } return ret; @@ -1133,8 +1133,8 @@ void EditorSelection::_update_nl() { selected_node_list.clear(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node *parent = E->key(); + for (const KeyValue<Node *, Object *> &E : selection) { + Node *parent = E.key; parent = parent->get_parent(); bool skip = false; while (parent) { @@ -1148,7 +1148,7 @@ void EditorSelection::_update_nl() { if (skip) { continue; } - selected_node_list.push_back(E->key()); + selected_node_list.push_back(E.key); } nl_changed = true; @@ -1183,8 +1183,8 @@ List<Node *> &EditorSelection::get_selected_node_list() { List<Node *> EditorSelection::get_full_selected_node_list() { List<Node *> node_list; - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - node_list.push_back(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + node_list.push_back(E.key); } return node_list; diff --git a/editor/editor_dir_dialog.cpp b/editor/editor_dir_dialog.cpp index 5df392b91e..f91dedf25d 100644 --- a/editor/editor_dir_dialog.cpp +++ b/editor/editor_dir_dialog.cpp @@ -44,6 +44,7 @@ void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p p_item->set_metadata(0, p_dir->get_path()); p_item->set_icon(0, tree->get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); + p_item->set_icon_modulate(0, tree->get_theme_color(SNAME("folder_icon_modulate"), SNAME("FileDialog"))); if (!p_item->get_parent()) { p_item->set_text(0, "res://"); diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 10ed76673e..2010ee01db 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -451,6 +451,9 @@ void EditorExportPlatform::_export_find_resources(EditorFileSystemDirectory *p_d } for (int i = 0; i < p_dir->get_file_count(); i++) { + if (p_dir->get_file_type(i) == "TextFile") { + continue; + } p_paths.insert(p_dir->get_file_path(i)); } } @@ -1043,17 +1046,19 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & return err; } } - if (FileAccess::exists(ResourceUID::CACHE_FILE)) { - Vector<uint8_t> array = FileAccess::get_file_as_array(ResourceUID::CACHE_FILE); - err = p_func(p_udata, ResourceUID::CACHE_FILE, array, idx, total, enc_in_filters, enc_ex_filters, key); + String resource_cache_file = ResourceUID::get_cache_file(); + if (FileAccess::exists(resource_cache_file)) { + Vector<uint8_t> array = FileAccess::get_file_as_array(resource_cache_file); + err = p_func(p_udata, resource_cache_file, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; } } - if (FileAccess::exists(NativeExtension::EXTENSION_LIST_CONFIG_FILE)) { - Vector<uint8_t> array = FileAccess::get_file_as_array(NativeExtension::EXTENSION_LIST_CONFIG_FILE); - err = p_func(p_udata, NativeExtension::EXTENSION_LIST_CONFIG_FILE, array, idx, total, enc_in_filters, enc_ex_filters, key); + String extension_list_config_file = NativeExtension::get_extension_list_config_file(); + if (FileAccess::exists(extension_list_config_file)) { + Vector<uint8_t> array = FileAccess::get_file_as_array(extension_list_config_file); + err = p_func(p_udata, extension_list_config_file, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; } @@ -1814,9 +1819,9 @@ bool EditorExportPlatformPC::can_export(const Ref<EditorExportPreset> &p_preset, List<String> EditorExportPlatformPC::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { List<String> list; - for (Map<String, String>::Element *E = extensions.front(); E; E = E->next()) { - if (p_preset->get(E->key())) { - list.push_back(extensions[E->key()]); + for (const KeyValue<String, String> &E : extensions) { + if (p_preset->get(E.key)) { + list.push_back(extensions[E.key]); return list; } } diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 84a9237a96..2222a5e5d3 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -179,9 +179,9 @@ Error EditorFeatureProfile::save_to_file(const String &p_path) { Array dis_props; - for (Map<StringName, Set<StringName>>::Element *E = disabled_properties.front(); E; E = E->next()) { - for (Set<StringName>::Element *F = E->get().front(); F; F = F->next()) { - dis_props.push_back(String(E->key()) + ":" + String(F->get())); + for (KeyValue<StringName, Set<StringName>> &E : disabled_properties) { + for (Set<StringName>::Element *F = E.value.front(); F; F = F->next()) { + dis_props.push_back(String(E.key) + ":" + String(F->get())); } } diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 8956983646..675234959a 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -588,7 +588,7 @@ void EditorFileDialog::_item_list_item_rmb_selected(int p_item, const Vector2 &p continue; } Dictionary item_meta = item_list->get_item_metadata(i); - if (String(item_meta["path"]).begins_with("res://.godot")) { + if (String(item_meta["path"]).begins_with(ProjectSettings::get_singleton()->get_project_data_path())) { allow_delete = false; break; } diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 8523833d52..767856f939 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -862,6 +862,9 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, DirAccess } else { //new or modified time fi->type = ResourceLoader::get_resource_type(path); + if (fi->type == "" && textfile_extensions.has(ext)) { + fi->type = "TextFile"; + } fi->uid = ResourceLoader::get_resource_uid(path); fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); fi->deps = _get_dependencies(path); @@ -984,6 +987,9 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const fi->modified_time = FileAccess::get_modified_time(path); fi->import_modified_time = 0; fi->type = ResourceLoader::get_resource_type(path); + if (fi->type == "" && textfile_extensions.has(ext)) { + fi->type = "TextFile"; + } fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); fi->import_valid = ResourceLoader::is_import_valid(path); fi->import_group_file = ResourceLoader::get_import_group_file(path); @@ -1072,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]; @@ -1539,6 +1545,9 @@ void EditorFileSystem::update_file(const String &p_file) { } String type = ResourceLoader::get_resource_type(p_file); + if (type == "" && textfile_extensions.has(p_file.get_extension())) { + type = "TextFile"; + } ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_file); if (cpos == -1) { @@ -1556,7 +1565,7 @@ void EditorFileSystem::update_file(const String &p_file) { EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); fi->file = file_name; fi->import_modified_time = 0; - fi->import_valid = ResourceLoader::is_import_valid(p_file); + fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file); if (idx == fs->files.size()) { fs->files.push_back(fi); @@ -1577,7 +1586,7 @@ void EditorFileSystem::update_file(const String &p_file) { fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(p_file); fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); fs->files[cpos]->deps = _get_dependencies(p_file); - fs->files[cpos]->import_valid = ResourceLoader::is_import_valid(p_file); + fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file); if (uid != ResourceUID::INVALID_ID) { if (ResourceUID::get_singleton()->has_id(uid)) { @@ -1654,8 +1663,8 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector Error err = importer->import_group_file(p_group_file, source_file_options, base_paths); //all went well, overwrite config files with proper remaps and md5s - for (Map<String, Map<StringName, Variant>>::Element *E = source_file_options.front(); E; E = E->next()) { - const String &file = E->key(); + for (const KeyValue<String, Map<StringName, Variant>> &E : source_file_options) { + const String &file = E.key; String base_path = ResourceFormatImporter::get_singleton()->get_import_base_path(file); FileAccessRef f = FileAccess::open(file + ".import", FileAccess::WRITE); ERR_FAIL_COND_V_MSG(!f, ERR_FILE_CANT_OPEN, "Cannot open import file '" + file + ".import'."); @@ -1740,6 +1749,9 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(file + ".import"); fs->files[cpos]->deps = _get_dependencies(file); fs->files[cpos]->type = importer->get_resource_type(); + if (fs->files[cpos]->type == "" && textfile_extensions.has(file.get_extension())) { + fs->files[cpos]->type = "TextFile"; + } fs->files[cpos]->import_valid = err == OK; //if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it @@ -2121,10 +2133,10 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { if (groups_to_reimport.size()) { Map<String, Vector<String>> group_files; _find_group_files(filesystem, group_files, groups_to_reimport); - for (Map<String, Vector<String>>::Element *E = group_files.front(); E; E = E->next()) { - Error err = _reimport_group(E->key(), E->get()); + for (const KeyValue<String, Vector<String>> &E : group_files) { + Error err = _reimport_group(E.key, E.value); if (err == OK) { - _reimport_file(E->key()); + _reimport_file(E.key); } } } @@ -2151,6 +2163,10 @@ Error EditorFileSystem::_resource_import(const String &p_path) { } bool EditorFileSystem::_should_skip_directory(const String &p_path) { + if (p_path.begins_with(ProjectSettings::get_singleton()->get_project_data_path())) { + return true; + } + if (FileAccess::exists(p_path.plus_file("project.godot"))) { // skip if another project inside this return true; @@ -2216,7 +2232,7 @@ void EditorFileSystem::move_group_file(const String &p_path, const String &p_new } ResourceUID::ID EditorFileSystem::_resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate) { - if (!p_path.is_resource_file() || p_path.begins_with("res://.godot")) { + if (!p_path.is_resource_file() || p_path.begins_with(ProjectSettings::get_singleton()->get_project_data_path())) { //saved externally (configuration file) or internal file, do not assign an ID. return ResourceUID::INVALID_ID; } @@ -2274,17 +2290,18 @@ bool EditorFileSystem::_scan_extensions() { } } + String extension_list_config_file = NativeExtension::get_extension_list_config_file(); if (extensions.size()) { if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed - FileAccessRef f = FileAccess::open(NativeExtension::EXTENSION_LIST_CONFIG_FILE, FileAccess::WRITE); + FileAccessRef f = FileAccess::open(extension_list_config_file, FileAccess::WRITE); for (const String &E : extensions) { f->store_line(E); } } } else { - if (loaded_extensions.size() || FileAccess::exists(NativeExtension::EXTENSION_LIST_CONFIG_FILE)) { //extensions were removed + if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { //extensions were removed DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - da->remove(NativeExtension::EXTENSION_LIST_CONFIG_FILE); + da->remove(extension_list_config_file); } } @@ -2329,6 +2346,7 @@ void EditorFileSystem::_bind_methods() { void EditorFileSystem::_update_extensions() { valid_extensions.clear(); import_extensions.clear(); + textfile_extensions.clear(); List<String> extensionsl; ResourceLoader::get_recognized_extensions_for_type("", &extensionsl); @@ -2336,6 +2354,15 @@ void EditorFileSystem::_update_extensions() { valid_extensions.insert(E); } + const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + for (const String &E : textfile_ext) { + if (valid_extensions.has(E)) { + continue; + } + valid_extensions.insert(E); + textfile_extensions.insert(E); + } + extensionsl.clear(); ResourceFormatImporter::get_singleton()->get_recognized_extensions(&extensionsl); for (const String &E : extensionsl) { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index b47cf5523a..feadd0f2b2 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -190,6 +190,7 @@ class EditorFileSystem : public Node { void _delete_internal_files(String p_file); + Set<String> textfile_extensions; Set<String> valid_extensions; Set<String> import_extensions; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index fff9e5e908..c049db8ef6 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -394,8 +394,8 @@ void EditorHelp::_update_doc() { bool prev = false; class_desc->push_font(doc_font); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { - if (E->get().inherits == cd.name) { + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { + if (E.value.inherits == cd.name) { if (!found) { class_desc->push_color(title_color); class_desc->add_text(TTR("Inherited by:") + " "); @@ -406,7 +406,7 @@ void EditorHelp::_update_doc() { class_desc->add_text(" , "); } - _add_type(E->get().name); + _add_type(E.value.name); prev = true; } } @@ -876,14 +876,14 @@ void EditorHelp::_update_doc() { class_desc->add_newline(); - for (Map<String, Vector<DocData::ConstantDoc>>::Element *E = enums.front(); E; E = E->next()) { - enum_line[E->key()] = class_desc->get_line_count() - 2; + for (KeyValue<String, Vector<DocData::ConstantDoc>> &E : enums) { + enum_line[E.key] = class_desc->get_line_count() - 2; class_desc->push_font(doc_code_font); class_desc->push_color(title_color); class_desc->add_text("enum "); class_desc->pop(); - String e = E->key(); + String e = E.key; if ((e.get_slice_count(".") > 1) && (e.get_slice(".", 0) == edited_class)) { e = e.get_slice(".", 1); } @@ -913,10 +913,10 @@ void EditorHelp::_update_doc() { } class_desc->push_indent(1); - Vector<DocData::ConstantDoc> enum_list = E->get(); + Vector<DocData::ConstantDoc> enum_list = E.value; Map<String, int> enumValuesContainer; - int enumStartingLine = enum_line[E->key()]; + int enumStartingLine = enum_line[E.key]; for (int i = 0; i < enum_list.size(); i++) { if (cd.name == "@GlobalScope") { @@ -955,7 +955,7 @@ void EditorHelp::_update_doc() { } if (cd.name == "@GlobalScope") { - enum_values_line[E->key()] = enumValuesContainer; + enum_values_line[E.key] = enumValuesContainer; } class_desc->pop(); @@ -1922,7 +1922,7 @@ void FindBar::_update_results_count() { return; } - String full_text = rich_text_label->get_text(); + String full_text = rich_text_label->get_parsed_text(); int from_pos = 0; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 4832cd6994..4d0f27c5d4 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -842,10 +842,10 @@ void EditorProperty::set_bottom_editor(Control *p_control) { bool EditorProperty::is_cache_valid() const { if (object) { - for (Map<StringName, Variant>::Element *E = cache.front(); E; E = E->next()) { + for (const KeyValue<StringName, Variant> &E : cache) { bool valid; - Variant value = object->get(E->key(), &valid); - if (!valid || value != E->get()) { + Variant value = object->get(E.key, &valid); + if (!valid || value != E.value) { return false; } } @@ -3029,8 +3029,8 @@ void EditorInspector::collapse_all_folding() { E->fold(); } - for (Map<StringName, List<EditorProperty *>>::Element *F = editor_property_map.front(); F; F = F->next()) { - for (EditorProperty *E : F->get()) { + for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { + for (EditorProperty *E : F.value) { E->collapse_all_folding(); } } @@ -3040,8 +3040,8 @@ void EditorInspector::expand_all_folding() { for (EditorInspectorSection *E : sections) { E->unfold(); } - for (Map<StringName, List<EditorProperty *>>::Element *F = editor_property_map.front(); F; F = F->next()) { - for (EditorProperty *E : F->get()) { + for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { + for (EditorProperty *E : F.value) { E->expand_all_folding(); } } @@ -3306,11 +3306,11 @@ void EditorInspector::_property_selected(const String &p_path, int p_focusable) property_selected = p_path; property_focusable = p_focusable; //deselect the others - for (Map<StringName, List<EditorProperty *>>::Element *F = editor_property_map.front(); F; F = F->next()) { - if (F->key() == property_selected) { + for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { + if (F.key == property_selected) { continue; } - for (EditorProperty *E : F->get()) { + for (EditorProperty *E : F.value) { if (E->is_selected()) { E->deselect(); } @@ -3368,8 +3368,8 @@ void EditorInspector::_notification(int p_what) { if (refresh_countdown > 0) { refresh_countdown -= get_process_delta_time(); if (refresh_countdown <= 0) { - for (Map<StringName, List<EditorProperty *>>::Element *F = editor_property_map.front(); F; F = F->next()) { - for (EditorProperty *E : F->get()) { + for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { + for (EditorProperty *E : F.value) { if (!E->is_cache_valid()) { E->update_property(); E->update_reload_status(); diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index f91cb7f607..346b93a87c 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -116,8 +116,8 @@ void EditorLog::_save_state() { config->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("editor_layout.cfg")); const String section = "editor_log"; - for (Map<MessageType, LogFilter *>::Element *E = type_filter_map.front(); E; E = E->next()) { - config->set_value(section, "log_filter_" + itos(E->key()), E->get()->is_active()); + for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) { + config->set_value(section, "log_filter_" + itos(E.key), E.value->is_active()); } config->set_value(section, "collapse", collapse); @@ -135,8 +135,8 @@ void EditorLog::_load_state() { // Run the below code even if config->load returns an error, since we want the defaults to be set even if the file does not exist yet. const String section = "editor_log"; - for (Map<MessageType, LogFilter *>::Element *E = type_filter_map.front(); E; E = E->next()) { - E->get()->set_active(config->get_value(section, "log_filter_" + itos(E->key()), true)); + for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) { + E.value->set_active(config->get_value(section, "log_filter_" + itos(E.key), true)); } collapse = config->get_value(section, "collapse", false); @@ -306,8 +306,8 @@ void EditorLog::_search_changed(const String &p_text) { } void EditorLog::_reset_message_counts() { - for (Map<MessageType, LogFilter *>::Element *E = type_filter_map.front(); E; E = E->next()) { - E->value()->set_message_count(0); + for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) { + E.value->set_message_count(0); } } @@ -441,7 +441,7 @@ void EditorLog::deinit() { } EditorLog::~EditorLog() { - for (Map<MessageType, LogFilter *>::Element *E = type_filter_map.front(); E; E = E->next()) { - memdelete(E->get()); + for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) { + memdelete(E.value); } } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index e719797e03..2f92f60d5e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -49,6 +49,7 @@ #include "core/version.h" #include "core/version_hash.gen.h" #include "main/main.h" +#include "scene/3d/importer_mesh_instance_3d.h" #include "scene/gui/center_container.h" #include "scene/gui/control.h" #include "scene/gui/dialogs.h" @@ -113,7 +114,6 @@ #include "editor/import/resource_importer_texture_atlas.h" #include "editor/import/resource_importer_wav.h" #include "editor/import/scene_import_settings.h" -#include "editor/import/scene_importer_mesh_node_3d.h" #include "editor/import_dock.h" #include "editor/multi_node_edit.h" #include "editor/node_dock.h" @@ -394,7 +394,7 @@ void EditorNode::_version_control_menu_option(int p_idx) { void EditorNode::_update_title() { const String appname = ProjectSettings::get_singleton()->get("application/config/name"); String title = (appname.is_empty() ? "Unnamed Project" : appname) + String(" - ") + VERSION_NAME; - const String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_filename() : String(); + const String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_scene_file_path() : String(); if (!edited.is_empty()) { // Display the edited scene name before the program name so that it can be seen in the OS task bar. title = vformat("%s - %s", edited.get_file(), title); @@ -741,6 +741,21 @@ void EditorNode::_notification(int p_what) { main_editor_buttons.write[i]->add_theme_font_size_override("font_size", gui_base->get_theme_font_size(SNAME("main_button_font_size"), SNAME("EditorFonts"))); } + Set<String> updated_textfile_extensions; + bool extensions_match = true; + const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + for (const String &E : textfile_ext) { + updated_textfile_extensions.insert(E); + if (extensions_match && !textfile_extensions.has(E)) { + extensions_match = false; + } + } + + if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size()) { + textfile_extensions = updated_textfile_extensions; + EditorFileSystem::get_singleton()->scan(); + } + _update_update_spinner(); } break; @@ -1114,7 +1129,13 @@ Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_d dependency_errors.clear(); Error err; - RES res = ResourceLoader::load(p_resource, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + + RES res; + if (ResourceLoader::exists(p_resource, "")) { + res = ResourceLoader::load(p_resource, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + } else if (textfile_extensions.has(p_resource.get_extension())) { + res = ScriptEditor::get_singleton()->open_file(p_resource); + } ERR_FAIL_COND_V(!res.is_valid(), ERR_CANT_OPEN); if (!p_ignore_broken_deps && dependency_errors.has(p_resource)) { @@ -1175,7 +1196,7 @@ void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String int srpos = path.find("::"); if (srpos != -1) { String base = path.substr(0, srpos); - if (!get_edited_scene() || get_edited_scene()->get_filename() != base) { + if (!get_edited_scene() || get_edited_scene()->get_scene_file_path() != base) { show_warning(TTR("This resource can't be saved because it does not belong to the edited scene. Make it unique first.")); return; } @@ -1549,7 +1570,7 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) { bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_node) { for (int i = 0; i < p_node->get_child_count(); i++) { Node *child = p_node->get_child(i); - if (child->get_filename() == p_filename) { + if (child->get_scene_file_path() == p_filename) { return true; } @@ -1645,7 +1666,7 @@ void EditorNode::_save_scene(String p_file, int idx) { return; } - if (scene->get_filename() != String() && _validate_scene_recursive(scene->get_filename(), scene)) { + if (scene->get_scene_file_path() != String() && _validate_scene_recursive(scene->get_scene_file_path(), scene)) { show_accept(TTR("This scene can't be saved because there is a cyclic instancing inclusion.\nPlease resolve it and then attempt to save again."), TTR("OK")); return; } @@ -1699,7 +1720,7 @@ void EditorNode::_save_scene(String p_file, int idx) { } if (err == OK) { - scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_file)); + scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_file)); if (idx < 0 || idx == editor_data.get_edited_scene()) { set_current_version(editor_data.get_undo_redo().get_version()); } else { @@ -1727,8 +1748,8 @@ void EditorNode::save_scene_list(Vector<String> p_scene_filenames) { for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { Node *scene = editor_data.get_edited_scene_root(i); - if (scene && (p_scene_filenames.find(scene->get_filename()) >= 0)) { - _save_scene(scene->get_filename(), i); + if (scene && (p_scene_filenames.find(scene->get_scene_file_path()) >= 0)) { + _save_scene(scene->get_scene_file_path(), i); } } } @@ -1738,7 +1759,7 @@ void EditorNode::restart_editor() { String to_reopen; if (get_tree()->get_edited_scene_root()) { - to_reopen = get_tree()->get_edited_scene_root()->get_filename(); + to_reopen = get_tree()->get_edited_scene_root()->get_scene_file_path(); } _exit_editor(); @@ -1755,17 +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_filename() != "") { - if (i != editor_data.get_edited_scene()) { - _save_scene(scene->get_filename(), 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_filename()); + all_saved = false; } - } // else: ignore new scenes + } } + if (!all_saved) { + show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes")); + } _save_default_environment(); } @@ -1776,7 +1805,7 @@ void EditorNode::_mark_unsaved_scenes() { continue; } - String path = node->get_filename(); + String path = node->get_scene_file_path(); if (!(path == String() || FileAccess::exists(path))) { if (i == editor_data.get_edited_scene()) { set_current_version(-1); @@ -2128,7 +2157,7 @@ void EditorNode::_edit_current() { if (FileAccess::exists(base_path + ".import")) { editable_warning = TTR("This resource belongs to a scene that was imported, so it's not editable.\nPlease read the documentation relevant to importing scenes to better understand this workflow."); } else { - if ((!get_edited_scene() || get_edited_scene()->get_filename() != base_path) && ResourceLoader::get_resource_type(base_path) == "PackedScene") { + if ((!get_edited_scene() || get_edited_scene()->get_scene_file_path() != base_path) && ResourceLoader::get_resource_type(base_path) == "PackedScene") { editable_warning = TTR("This resource belongs to a scene that was instantiated or inherited.\nChanges to it won't be kept when saving the current scene."); } } @@ -2152,8 +2181,8 @@ void EditorNode::_edit_current() { inspector_dock->update(nullptr); } - if (get_edited_scene() && get_edited_scene()->get_filename() != String()) { - String source_scene = get_edited_scene()->get_filename(); + if (get_edited_scene() && get_edited_scene()->get_scene_file_path() != String()) { + String source_scene = get_edited_scene()->get_scene_file_path(); if (FileAccess::exists(source_scene + ".import")) { editable_warning = TTR("This scene was imported, so changes to it won't be kept.\nInstancing it or inheriting will allow making changes to it.\nPlease read the documentation relevant to importing scenes to better understand this workflow."); } @@ -2276,7 +2305,7 @@ void EditorNode::_run(bool p_current, const String &p_custom) { String args; bool skip_breakpoints; - if (p_current || (editor_data.get_edited_scene_root() && p_custom != String() && p_custom == editor_data.get_edited_scene_root()->get_filename())) { + if (p_current || (editor_data.get_edited_scene_root() && p_custom != String() && p_custom == editor_data.get_edited_scene_root()->get_scene_file_path())) { Node *scene = editor_data.get_edited_scene_root(); if (!scene) { @@ -2284,7 +2313,7 @@ void EditorNode::_run(bool p_current, const String &p_custom) { return; } - if (scene->get_filename() == "") { + if (scene->get_scene_file_path() == "") { current_option = -1; _menu_option(FILE_SAVE_AS_SCENE); // Set the option to save and run so when the dialog is accepted, the scene runs. @@ -2293,7 +2322,7 @@ void EditorNode::_run(bool p_current, const String &p_custom) { return; } - run_filename = scene->get_filename(); + run_filename = scene->get_scene_file_path(); } else if (p_custom != "") { run_filename = p_custom; } @@ -2309,8 +2338,8 @@ void EditorNode::_run(bool p_current, const String &p_custom) { if (unsaved_cache) { Node *scene = editor_data.get_edited_scene_root(); - if (scene && scene->get_filename() != "") { // Only autosave if there is a scene and if it has a path. - _save_scene_with_preview(scene->get_filename()); + if (scene && scene->get_scene_file_path() != "") { // Only autosave if there is a scene and if it has a path. + _save_scene_with_preview(scene->get_scene_file_path()); } } _menu_option(FILE_SAVE_ALL_SCENES); @@ -2403,7 +2432,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { Node *scene = editor_data.get_edited_scene_root(); if (scene) { - file->set_current_path(scene->get_filename()); + file->set_current_path(scene->get_scene_file_path()); }; file->set_title(p_option == FILE_OPEN_SCENE ? TTR("Open Scene") : TTR("Open Base Scene")); file->popup_file_dialog(); @@ -2465,7 +2494,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (unsaved_cache || p_option == FILE_CLOSE_ALL_AND_QUIT || p_option == FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER) { Node *scene_root = editor_data.get_edited_scene_root(tab_closing); if (scene_root) { - String scene_filename = scene_root->get_filename(); + String scene_filename = scene_root->get_scene_file_path(); save_confirmation->get_ok_button()->set_text(TTR("Save & Close")); save_confirmation->set_text(vformat(TTR("Save changes to '%s' before closing?"), scene_filename != "" ? scene_filename : "unsaved scene")); save_confirmation->popup_centered(); @@ -2484,20 +2513,22 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case SCENE_TAB_CLOSE: case FILE_SAVE_SCENE: { int scene_idx = (p_option == FILE_SAVE_SCENE) ? -1 : tab_closing; - Node *scene = editor_data.get_edited_scene_root(scene_idx); - if (scene && scene->get_filename() != "") { - if (scene_idx != editor_data.get_edited_scene()) { - _save_scene_with_preview(scene->get_filename(), scene_idx); - } else { - _save_scene_with_preview(scene->get_filename()); - } + if (scene && scene->get_scene_file_path() != "") { + if (DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { + if (scene_idx != editor_data.get_edited_scene()) { + _save_scene_with_preview(scene->get_scene_file_path(), scene_idx); + } else { + _save_scene_with_preview(scene->get_scene_file_path()); + } - if (scene_idx != -1) { - _discard_changes(); + if (scene_idx != -1) { + _discard_changes(); + } + save_layout(); + } else { + show_save_accept(vformat(TTR("%s no longer exists! Please specify a new save location."), scene->get_scene_file_path().get_base_dir()), TTR("OK")); } - save_layout(); - break; } [[fallthrough]]; @@ -2539,8 +2570,8 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } - if (scene->get_filename() != "") { - String path = scene->get_filename(); + if (scene->get_scene_file_path() != "") { + String path = scene->get_scene_file_path(); file->set_current_path(path); if (extensions.size()) { String ext = path.get_extension().to_lower(); @@ -2638,7 +2669,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { break; } - String filename = scene->get_filename(); + String filename = scene->get_scene_file_path(); if (filename == String()) { show_warning(TTR("Can't reload a scene that was never saved.")); @@ -2760,7 +2791,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { String unsaved_scenes; int i = _next_unsaved_scene(true, 0); while (i != -1) { - unsaved_scenes += "\n " + editor_data.get_edited_scene_root(i)->get_filename(); + unsaved_scenes += "\n " + editor_data.get_edited_scene_root(i)->get_scene_file_path(); i = _next_unsaved_scene(true, ++i); } @@ -2835,7 +2866,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { Node *scene = editor_data.get_edited_scene_root(); if (scene) { - file->set_current_path(scene->get_filename()); + file->set_current_path(scene->get_scene_file_path()); }; file->set_title(TTR("Pick a Main Scene")); file->popup_file_dialog(); @@ -2937,7 +2968,7 @@ int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) { int current = editor_data.get_edited_scene(); bool unsaved = (i == current) ? saved_version != editor_data.get_undo_redo().get_version() : editor_data.get_scene_version(i) != 0; if (unsaved) { - String scene_filename = editor_data.get_edited_scene_root(i)->get_filename(); + String scene_filename = editor_data.get_edited_scene_root(i)->get_scene_file_path(); if (p_valid_filename && scene_filename.length() == 0) { continue; } @@ -2969,7 +3000,7 @@ void EditorNode::_discard_changes(const String &p_str) { case SCENE_TAB_CLOSE: { Node *scene = editor_data.get_edited_scene_root(tab_closing); if (scene != nullptr) { - String scene_filename = scene->get_filename(); + String scene_filename = scene->get_scene_file_path(); if (scene_filename != "") { previous_scenes.push_back(scene_filename); } @@ -3177,8 +3208,8 @@ void EditorNode::_update_addon_config() { Vector<String> enabled_addons; - for (Map<String, EditorPlugin *>::Element *E = plugin_addons.front(); E; E = E->next()) { - enabled_addons.push_back(E->key()); + for (const KeyValue<String, EditorPlugin *> &E : plugin_addons) { + enabled_addons.push_back(E.key); } if (enabled_addons.size() == 0) { @@ -3560,9 +3591,9 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b dependency_errors.erase(lpath); //at least not self path - for (Map<String, Set<String>>::Element *E = dependency_errors.front(); E; E = E->next()) { - String txt = vformat(TTR("Scene '%s' has broken dependencies:"), E->key()) + "\n"; - for (Set<String>::Element *F = E->get().front(); F; F = F->next()) { + for (KeyValue<String, Set<String>> &E : dependency_errors) { + String txt = vformat(TTR("Scene '%s' has broken dependencies:"), E.key) + "\n"; + for (Set<String>::Element *F = E.value.front(); F; F = F->next()) { txt += "\t" + F->get() + "\n"; } add_io_error(txt); @@ -3598,7 +3629,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b Ref<SceneState> state = sdata->get_state(); state->set_path(lpath); new_scene->set_scene_inherited_state(state); - new_scene->set_filename(String()); + new_scene->set_scene_file_path(String()); } new_scene->set_scene_instance_state(Ref<SceneState>()); @@ -3771,7 +3802,7 @@ void EditorNode::_load_error_notify(void *p_ud, const String &p_text) { } bool EditorNode::_find_scene_in_use(Node *p_node, const String &p_path) const { - if (p_node->get_filename() == p_path) { + if (p_node->get_scene_file_path() == p_path) { return true; } @@ -3817,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); @@ -3827,8 +3859,6 @@ void EditorNode::register_editor_types() { GDREGISTER_CLASS(EditorSpinSlider); GDREGISTER_CLASS(EditorResourcePicker); GDREGISTER_CLASS(EditorScriptPicker); - GDREGISTER_CLASS(EditorSceneImporterMesh); - GDREGISTER_CLASS(EditorSceneImporterMeshNode3D); GDREGISTER_VIRTUAL_CLASS(FileSystemDock); @@ -3949,7 +3979,7 @@ void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_na pick_main_scene->hide(); current_option = SETTINGS_PICK_MAIN_SCENE; - _dialog_action(scene->get_filename()); + _dialog_action(scene->get_scene_file_path()); } } @@ -4025,8 +4055,8 @@ Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p } const Map<String, Vector<EditorData::CustomType>> &p_map = EditorNode::get_editor_data().get_custom_types(); - for (const Map<String, Vector<EditorData::CustomType>>::Element *E = p_map.front(); E; E = E->next()) { - const Vector<EditorData::CustomType> &ct = E->value(); + for (const KeyValue<String, Vector<EditorData::CustomType>> &E : p_map) { + const Vector<EditorData::CustomType> &ct = E.value; for (int i = 0; i < ct.size(); ++i) { if (ct[i].name == p_class) { if (ct[i].icon.is_valid()) { @@ -4150,6 +4180,13 @@ void EditorNode::show_accept(const String &p_text, const String &p_title) { accept->popup_centered(); } +void EditorNode::show_save_accept(const String &p_text, const String &p_title) { + current_option = -1; + save_accept->get_ok_button()->set_text(p_title); + save_accept->set_text(p_text); + save_accept->popup_centered(); +} + void EditorNode::show_warning(const String &p_text, const String &p_title) { if (warning->is_inside_tree()) { warning->set_text(p_text); @@ -4932,7 +4969,7 @@ void EditorNode::_scene_tab_closed(int p_tab, int option) { editor_data.get_scene_version(p_tab) != 0; if (unsaved) { save_confirmation->get_ok_button()->set_text(TTR("Save & Close")); - save_confirmation->set_text(vformat(TTR("Save changes to '%s' before closing?"), scene->get_filename() != "" ? scene->get_filename() : "unsaved scene")); + save_confirmation->set_text(vformat(TTR("Save changes to '%s' before closing?"), scene->get_scene_file_path() != "" ? scene->get_scene_file_path() : "unsaved scene")); save_confirmation->popup_centered(); } else { _discard_changes(); @@ -5899,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); @@ -5907,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); } @@ -5984,6 +6021,11 @@ EditorNode::EditorNode() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle", PROPERTY_USAGE_DEFAULT)); EDITOR_DEF("run/auto_save/save_before_running", true); + 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); + } + theme_base = memnew(Control); add_child(theme_base); theme_base->set_anchors_and_offsets_preset(Control::PRESET_WIDE); @@ -6184,12 +6226,12 @@ EditorNode::EditorNode() { scene_tabs->set_min_width(int(EDITOR_DEF("interface/scene_tabs/minimum_width", 50)) * EDSCALE); scene_tabs->set_drag_to_rearrange_enabled(true); scene_tabs->connect("tab_changed", callable_mp(this, &EditorNode::_scene_tab_changed)); - scene_tabs->connect("right_button_pressed", callable_mp(this, &EditorNode::_scene_tab_script_edited)); + scene_tabs->connect("tab_rmb_clicked", callable_mp(this, &EditorNode::_scene_tab_script_edited)); scene_tabs->connect("tab_closed", callable_mp(this, &EditorNode::_scene_tab_closed), varray(SCENE_TAB_CLOSE)); scene_tabs->connect("tab_hovered", callable_mp(this, &EditorNode::_scene_tab_hovered)); scene_tabs->connect("mouse_exited", callable_mp(this, &EditorNode::_scene_tab_exit)); scene_tabs->connect("gui_input", callable_mp(this, &EditorNode::_scene_tab_input)); - scene_tabs->connect("reposition_active_tab_request", callable_mp(this, &EditorNode::_reposition_active_tab)); + scene_tabs->connect("active_tab_rearranged", callable_mp(this, &EditorNode::_reposition_active_tab)); scene_tabs->connect("resized", callable_mp(this, &EditorNode::_update_scene_tabs)); tabbar_container = memnew(HBoxContainer); @@ -6263,6 +6305,10 @@ EditorNode::EditorNode() { gui_base->add_child(accept); accept->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current)); + save_accept = memnew(AcceptDialog); + gui_base->add_child(save_accept); + save_accept->connect("confirmed", callable_mp(this, &EditorNode::_menu_option), make_binds((int)MenuOptions::FILE_SAVE_AS_SCENE)); + project_export = memnew(ProjectExportDialog); gui_base->add_child(project_export); @@ -6889,7 +6935,7 @@ EditorNode::EditorNode() { add_child(editor_interface); //more visually meaningful to have this later - raise_bottom_panel_item(AnimationPlayerEditor::singleton); + raise_bottom_panel_item(AnimationPlayerEditor::get_singleton()); add_editor_plugin(VersionControlEditorPlugin::get_singleton()); add_editor_plugin(memnew(ShaderEditorPlugin(this))); @@ -7163,20 +7209,24 @@ bool EditorPluginList::forward_gui_input(const Ref<InputEvent> &p_event) { return discard; } -bool EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) { - bool discard = false; +EditorPlugin::AfterGUIInput EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) { + EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; for (int i = 0; i < plugins_list.size(); i++) { if ((!serve_when_force_input_enabled) && plugins_list[i]->is_input_event_forwarding_always_enabled()) { continue; } - if (plugins_list[i]->forward_spatial_gui_input(p_camera, p_event)) { - discard = true; + EditorPlugin::AfterGUIInput current_after = plugins_list[i]->forward_spatial_gui_input(p_camera, p_event); + if (current_after == EditorPlugin::AFTER_GUI_INPUT_STOP) { + after = EditorPlugin::AFTER_GUI_INPUT_STOP; + } + if (after != EditorPlugin::AFTER_GUI_INPUT_STOP && current_after == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; } } - return discard; + return after; } void EditorPluginList::forward_canvas_draw_over_viewport(Control *p_overlay) { diff --git a/editor/editor_node.h b/editor/editor_node.h index 2e8b850c7b..73feeecfee 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -308,6 +308,7 @@ private: ConfirmationDialog *pick_main_scene; Button *select_current_scene_button; AcceptDialog *accept; + AcceptDialog *save_accept; EditorAbout *about; AcceptDialog *warning; @@ -541,6 +542,7 @@ private: String import_reload_fn; + Set<String> textfile_extensions; Set<FileDialog *> file_dialogs; Set<EditorFileDialog *> editor_file_dialogs; @@ -814,6 +816,7 @@ public: Ref<Texture2D> get_class_icon(const String &p_class, const String &p_fallback = "Object") const; void show_accept(const String &p_text, const String &p_title); + void show_save_accept(const String &p_text, const String &p_title); void show_warning(const String &p_text, const String &p_title = TTR("Warning!")); void _copy_warning(const String &p_str); @@ -939,7 +942,7 @@ public: bool forward_gui_input(const Ref<InputEvent> &p_event); void forward_canvas_draw_over_viewport(Control *p_overlay); void forward_canvas_force_draw_over_viewport(Control *p_overlay); - bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled); + EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled); void forward_spatial_draw_over_viewport(Control *p_overlay); void forward_spatial_force_draw_over_viewport(Control *p_overlay); void add_plugin(EditorPlugin *p_plugin); diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp index c9817190dd..5b48cc2638 100644 --- a/editor/editor_paths.cpp +++ b/editor/editor_paths.cpp @@ -87,6 +87,8 @@ void EditorPaths::_bind_methods() { EditorPaths::EditorPaths() { singleton = this; + project_data_dir = ProjectSettings::get_singleton()->get_project_data_path(); + // Self-contained mode if a `._sc_` or `_sc_` file is present in executable dir. String exe_path = OS::get_singleton()->get_executable_path().get_base_dir(); { @@ -183,7 +185,7 @@ EditorPaths::EditorPaths() { } } - // Validate or create project-specific editor data dir (`res://.godot`), + // Validate or create project-specific editor data dir, // including shader cache subdir. if (Main::is_project_manager() || Main::is_cmdline_tool()) { @@ -198,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. @@ -205,8 +221,9 @@ EditorPaths::EditorPaths() { dir_res->make_dir("editor"); } // Imported assets dir. - if (!dir_res->dir_exists(ProjectSettings::IMPORTED_FILES_PATH)) { - dir_res->make_dir(ProjectSettings::IMPORTED_FILES_PATH); + String imported_files_path = ProjectSettings::get_singleton()->get_imported_files_path(); + if (!dir_res->dir_exists(imported_files_path)) { + dir_res->make_dir(imported_files_path); } } } diff --git a/editor/editor_paths.h b/editor/editor_paths.h index 2c156b7c96..cf94ed797a 100644 --- a/editor/editor_paths.h +++ b/editor/editor_paths.h @@ -41,7 +41,7 @@ class EditorPaths : public Object { String data_dir; // Editor data (templates, shader cache, etc.). String config_dir; // Editor config (settings, profiles, themes, etc.). String cache_dir; // Editor cache (thumbnails, tmp generated files). - String project_data_dir = "res://.godot"; // Project-specific data (metadata, shader cache, etc.). + String project_data_dir; // Project-specific data (metadata, shader cache, etc.). bool self_contained = false; // Self-contained means everything goes to `editor_data` dir. String self_contained_file; // Self-contained file with configuration. diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 99b917107e..61c01993ae 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -219,7 +219,7 @@ Array EditorInterface::get_open_scenes() const { if (scenes[idx_scn].root == nullptr) { continue; } - ret.push_back(scenes[idx_scn].root->get_filename()); + ret.push_back(scenes[idx_scn].root->get_scene_file_path()); } return ret; } @@ -291,11 +291,11 @@ Error EditorInterface::save_scene() { if (!get_edited_scene_root()) { return ERR_CANT_CREATE; } - if (get_edited_scene_root()->get_filename() == String()) { + if (get_edited_scene_root()->get_scene_file_path() == String()) { return ERR_CANT_CREATE; } - save_scene_as(get_edited_scene_root()->get_filename()); + save_scene_as(get_edited_scene_root()->get_scene_file_path()); return OK; } @@ -593,14 +593,14 @@ int EditorPlugin::update_overlays() const { } } -bool EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { - bool success; +EditorPlugin::AfterGUIInput EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { + int success; if (GDVIRTUAL_CALL(_forward_3d_gui_input, p_camera, p_event, success)) { - return success; + return static_cast<EditorPlugin::AfterGUIInput>(success); } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } void EditorPlugin::forward_spatial_draw_over_viewport(Control *p_overlay) { @@ -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 169106d901..278059f8c4 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -151,7 +151,7 @@ protected: GDVIRTUAL1R(bool, _forward_canvas_gui_input, Ref<InputEvent>) GDVIRTUAL1(_forward_canvas_draw_over_viewport, Control *) GDVIRTUAL1(_forward_canvas_force_draw_over_viewport, Control *) - GDVIRTUAL2R(bool, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>) + GDVIRTUAL2R(int, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>) GDVIRTUAL1(_forward_3d_draw_over_viewport, Control *) GDVIRTUAL1(_forward_3d_force_draw_over_viewport, Control *) GDVIRTUAL0RC(String, _get_plugin_name) @@ -200,6 +200,12 @@ public: DOCK_SLOT_MAX }; + enum AfterGUIInput { + AFTER_GUI_INPUT_PASS, + AFTER_GUI_INPUT_STOP, + AFTER_GUI_INPUT_DESELECT + }; + //TODO: send a resource for editing to the editor node? void add_control_to_container(CustomControlContainer p_location, Control *p_control); @@ -228,7 +234,7 @@ public: virtual void forward_canvas_draw_over_viewport(Control *p_overlay); virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay); - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); virtual void forward_spatial_draw_over_viewport(Control *p_overlay); virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay); @@ -282,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_properties.cpp b/editor/editor_properties.cpp index c0dadc4484..2d4a3ac788 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -3574,7 +3574,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ case Variant::PACKED_COLOR_ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); editor->setup(Variant::PACKED_COLOR_ARRAY); - + return editor; } break; default: { } diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 9cecb62c66..9b5dc8851c 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -429,6 +429,12 @@ void EditorPropertyArray::_button_draw() { bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const { String allowed_type = Variant::get_type_name(subtype); + // When the subtype is of type Object, an additional subtype may be specified in the hint string + // (e.g. Resource, Texture2D, ShaderMaterial, etc). We want the allowed type to be that, not just "Object". + if (subtype == Variant::OBJECT && subtype_hint_string != "") { + allowed_type = subtype_hint_string; + } + Dictionary drag_data = p_drag_data; if (drag_data.has("type") && String(drag_data["type"]) == "files") { diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index 8fc1345f3e..8783fe4fc0 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -439,7 +439,7 @@ void EditorResourcePreview::stop() { preview_sem.post(); while (!exited.is_set()) { OS::get_singleton()->delay_usec(10000); - RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on visual server + RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on rendering server } thread.wait_to_finish(); } diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index 5828549bdc..74ebffc404 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -66,9 +66,9 @@ void EditorRunNative::_notification(int p_what) { bool changed = EditorExport::get_singleton()->poll_export_platforms() || first; if (changed) { - for (Map<int, MenuButton *>::Element *E = menus.front(); E; E = E->next()) { - Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(E->key()); - MenuButton *mb = E->get(); + for (KeyValue<int, MenuButton *> &E : menus) { + Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(E.key); + MenuButton *mb = E.value; int dc = eep->get_options_count(); if (dc == 0) { diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 1820804cfe..223cc6650c 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -73,14 +73,15 @@ bool EditorSettings::_set_only(const StringName &p_name, const Variant &p_value) if (p_name == "shortcuts") { Array arr = p_value; - ERR_FAIL_COND_V(arr.size() && arr.size() & 1, true); - for (int i = 0; i < arr.size(); i += 2) { - String name = arr[i]; - Ref<InputEvent> shortcut = arr[i + 1]; + for (int i = 0; i < arr.size(); i++) { + Dictionary dict = arr[i]; + String name = dict["name"]; + + Array shortcut_events = dict["shortcuts"]; Ref<Shortcut> sc; sc.instantiate(); - sc->set_event(shortcut); + sc->set_events(shortcut_events); add_shortcut(name, sc); } @@ -138,47 +139,75 @@ bool EditorSettings::_get(const StringName &p_name, Variant &r_ret) const { _THREAD_SAFE_METHOD_ if (p_name == "shortcuts") { - Array arr; - for (const Map<String, Ref<Shortcut>>::Element *E = shortcuts.front(); E; E = E->next()) { - Ref<Shortcut> sc = E->get(); + 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(E->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(); - Ref<InputEvent> original = sc->get_meta("original"); - if (sc->matches_event(original) || (original.is_null() && sc->get_event().is_null())) { - continue; //not changed from default, don't save - } + 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; } - arr.push_back(E->key()); - arr.push_back(sc->get_event()); + Array original_events = sc->get_meta("original"); + + bool is_same = Shortcut::is_event_array_equal(original_events, shortcut_events); + if (is_same) { + continue; // Not changed from default; don't save. + } + + save_array.push_back(dict); } - r_ret = arr; + r_ret = save_array; return true; } else if (p_name == "builtin_action_overrides") { Array actions_arr; - for (Map<String, List<Ref<InputEvent>>>::Element *E = builtin_action_overrides.front(); E; E = E->next()) { - List<Ref<InputEvent>> events = E->get(); + for (const KeyValue<String, List<Ref<InputEvent>>> &action_override : builtin_action_overrides) { + List<Ref<InputEvent>> events = action_override.value; - // TODO: skip actions which are the same as the builtin. Dictionary action_dict; - action_dict["name"] = E->key(); + action_dict["name"] = action_override.key; + // Convert the list to an array, and only keep key events as this is for the editor. Array events_arr; - for (List<Ref<InputEvent>>::Element *I = events.front(); I; I = I->next()) { - events_arr.push_back(I->get()); + for (const Ref<InputEvent> &ie : events) { + Ref<InputEventKey> iek = ie; + if (iek.is_valid()) { + events_arr.append(iek); + } } - action_dict["events"] = events_arr; + Array defaults_arr; + List<Ref<InputEvent>> defaults = InputMap::get_singleton()->get_builtins()[action_override.key]; + for (const Ref<InputEvent> &default_input_event : defaults) { + if (default_input_event.is_valid()) { + defaults_arr.append(default_input_event); + } + } + + bool same = Shortcut::is_event_array_equal(events_arr, defaults_arr); + + // Don't save if same as default. + if (same) { + continue; + } + action_dict["events"] = events_arr; actions_arr.push_back(action_dict); } @@ -411,6 +440,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("interface/editor/automatically_open_screenshots", true); EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/single_window_mode", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) _initial_set("interface/editor/hide_console_window", false); + _initial_set("interface/editor/mouse_extra_buttons_navigate_history", true); _initial_set("interface/editor/save_each_scene_on_quit", true); // Regression // Inspector @@ -455,10 +485,12 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // SceneTree _initial_set("docks/scene_tree/start_create_dialog_fully_expanded", false); + _initial_set("docks/scene_tree/auto_expand_to_selected", true); // FileSystem EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "docks/filesystem/thumbnail_size", 64, "32,128,16") _initial_set("docks/filesystem/always_show_folders", true); + _initial_set("docks/filesystem/textfile_extensions", "txt,md,cfg,ini,log,json,yml,yaml,toml"); // Property editor _initial_set("docks/property_editor/auto_refresh_interval", 0.2); //update 5 times per second by default @@ -1403,7 +1435,7 @@ Ref<Shortcut> EditorSettings::get_shortcut(const String &p_name) const { const Map<String, List<Ref<InputEvent>>>::Element *builtin_override = builtin_action_overrides.find(p_name); if (builtin_override) { sc.instantiate(); - sc->set_event(builtin_override->get().front()->get()); + sc->set_events_list(&builtin_override->get()); sc->set_name(InputMap::get_singleton()->get_builtin_display_name(p_name)); } @@ -1412,7 +1444,7 @@ Ref<Shortcut> EditorSettings::get_shortcut(const String &p_name) const { const OrderedHashMap<String, List<Ref<InputEvent>>>::ConstElement builtin_default = InputMap::get_singleton()->get_builtins_with_feature_overrides_applied().find(p_name); if (builtin_default) { sc.instantiate(); - sc->set_event(builtin_default.get().front()->get()); + sc->set_events_list(&builtin_default.get()); sc->set_name(InputMap::get_singleton()->get_builtin_display_name(p_name)); } } @@ -1427,8 +1459,8 @@ Ref<Shortcut> EditorSettings::get_shortcut(const String &p_name) const { } void EditorSettings::get_shortcut_list(List<String> *r_shortcuts) { - for (const Map<String, Ref<Shortcut>>::Element *E = shortcuts.front(); E; E = E->next()) { - r_shortcuts->push_back(E->key()); + for (const KeyValue<String, Ref<Shortcut>> &E : shortcuts) { + r_shortcuts->push_back(E.key); } } @@ -1448,52 +1480,91 @@ void ED_SHORTCUT_OVERRIDE(const String &p_path, const String &p_feature, Key p_k Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_path); ERR_FAIL_COND_MSG(!sc.is_valid(), "Used ED_SHORTCUT_OVERRIDE with invalid shortcut: " + p_path + "."); + PackedInt32Array arr; + arr.push_back(p_keycode); + + ED_SHORTCUT_OVERRIDE_ARRAY(p_path, p_feature, arr); +} + +void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, const PackedInt32Array &p_keycodes) { + Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_path); + ERR_FAIL_COND_MSG(!sc.is_valid(), "Used ED_SHORTCUT_OVERRIDE_ARRAY with invalid shortcut: " + p_path + "."); + // Only add the override if the OS supports the provided feature. - if (OS::get_singleton()->has_feature(p_feature)) { - Ref<InputEventKey> ie; - if (p_keycode) { - ie = InputEventKey::create_reference(p_keycode); + if (!OS::get_singleton()->has_feature(p_feature)) { + return; + } + + Array events; + + for (int i = 0; i < p_keycodes.size(); i++) { + Key keycode = (Key)p_keycodes[i]; + +#ifdef OSX_ENABLED + // Use Cmd+Backspace as a general replacement for Delete shortcuts on macOS + if (keycode == KEY_DELETE) { + keycode = KEY_MASK_CMD | KEY_BACKSPACE; } +#endif - // Directly override the existing shortcut. - sc->set_event(ie); - sc->set_meta("original", ie); + Ref<InputEventKey> ie; + if (keycode) { + ie = InputEventKey::create_reference(keycode); + events.push_back(ie); + } } + + // Directly override the existing shortcut. + sc->set_events(events); + sc->set_meta("original", events.duplicate(true)); } Ref<Shortcut> ED_SHORTCUT(const String &p_path, const String &p_name, Key p_keycode) { + PackedInt32Array arr; + arr.push_back(p_keycode); + return ED_SHORTCUT_ARRAY(p_path, p_name, arr); +} + +Ref<Shortcut> ED_SHORTCUT_ARRAY(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes) { + Array events; + + for (int i = 0; i < p_keycodes.size(); i++) { + Key keycode = (Key)p_keycodes[i]; + #ifdef OSX_ENABLED - // Use Cmd+Backspace as a general replacement for Delete shortcuts on macOS - if (p_keycode == KEY_DELETE) { - p_keycode = KEY_MASK_CMD | KEY_BACKSPACE; - } + // Use Cmd+Backspace as a general replacement for Delete shortcuts on macOS + if (keycode == KEY_DELETE) { + keycode = KEY_MASK_CMD | KEY_BACKSPACE; + } #endif - Ref<InputEventKey> ie; - if (p_keycode) { - ie = InputEventKey::create_reference(p_keycode); + Ref<InputEventKey> ie; + if (keycode) { + ie = InputEventKey::create_reference(keycode); + events.push_back(ie); + } } if (!EditorSettings::get_singleton()) { Ref<Shortcut> sc; sc.instantiate(); sc->set_name(p_name); - sc->set_event(ie); - sc->set_meta("original", ie); + sc->set_events(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", ie); //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_event(ie); - sc->set_meta("original", ie); //to compare against changes + sc->set_events(events); + sc->set_meta("original", events.duplicate(true)); //to compare against changes EditorSettings::get_singleton()->add_shortcut(p_path, sc); return sc; @@ -1548,7 +1619,7 @@ void EditorSettings::set_builtin_action_override(const String &p_name, const Arr // Update the shortcut (if it is used somewhere in the editor) to be the first event of the new list. if (shortcuts.has(p_name)) { - shortcuts[p_name]->set_event(event_list.front()->get()); + shortcuts[p_name]->set_events_list(&event_list); } } diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 9067539e29..04e227bc5c 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -201,7 +201,9 @@ Variant _EDITOR_GET(const String &p_setting); #define ED_IS_SHORTCUT(p_name, p_ev) (EditorSettings::get_singleton()->is_shortcut(p_name, p_ev)) Ref<Shortcut> ED_SHORTCUT(const String &p_path, const String &p_name, Key p_keycode = KEY_NONE); +Ref<Shortcut> ED_SHORTCUT_ARRAY(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes); void ED_SHORTCUT_OVERRIDE(const String &p_path, const String &p_feature, Key p_keycode = KEY_NONE); +void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, const PackedInt32Array &p_keycodes); Ref<Shortcut> ED_GET_SHORTCUT(const String &p_path); #endif // EDITOR_SETTINGS_H diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index afeba4f6fb..ec90af1bcc 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -191,6 +191,59 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) { } } +void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventKey> k = p_event; + if (k.is_valid() && k->is_pressed()) { + double step = get_step(); + double real_step = step; + if (step < 1) { + double divisor = 1.0 / get_step(); + + if (trunc(divisor) == divisor) { + step = 1.0; + } + } + + if (k->is_ctrl_pressed()) { + step *= 100.0; + } else if (k->is_shift_pressed()) { + step *= 10.0; + } else if (k->is_alt_pressed()) { + step *= 0.1; + } + + uint32_t code = k->get_keycode(); + switch (code) { + case KEY_UP: { + _evaluate_input_text(); + + double last_value = get_value(); + set_value(last_value + step); + double new_value = get_value(); + + if (new_value < CLAMP(last_value + step, get_min(), get_max())) { + set_value(last_value + real_step); + } + + value_input->set_text(get_text_value()); + } break; + case KEY_DOWN: { + _evaluate_input_text(); + + double last_value = get_value(); + set_value(last_value - step); + double new_value = get_value(); + + if (new_value > CLAMP(last_value - step, get_min(), get_max())) { + set_value(last_value - real_step); + } + + value_input->set_text(get_text_value()); + } break; + } + } +} + void EditorSpinSlider::_update_value_input_stylebox() { if (!value_input) { return; @@ -277,9 +330,8 @@ void EditorSpinSlider::_draw_spin_slider() { float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep); Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs); - const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(num_rid); - int v_size = visual.size(); - const TextServer::Glyph *glyphs = visual.ptr(); + int v_size = TS->shaped_text_get_glyph_count(num_rid); + const Glyph *glyphs = TS->shaped_text_get_glyphs(num_rid); for (int i = 0; i < v_size; i++) { for (int j = 0; j < glyphs[i].repeat; j++) { if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) { @@ -585,11 +637,13 @@ void EditorSpinSlider::_ensure_input_popup() { value_input_popup->connect("popup_hide", callable_mp(this, &EditorSpinSlider::_value_input_closed)); value_input->connect("text_submitted", callable_mp(this, &EditorSpinSlider::_value_input_submitted)); value_input->connect("focus_exited", callable_mp(this, &EditorSpinSlider::_value_focus_exited)); + value_input->connect("gui_input", callable_mp(this, &EditorSpinSlider::_value_input_gui_input)); if (is_inside_tree()) { _update_value_input_stylebox(); } } + EditorSpinSlider::EditorSpinSlider() { flat = false; grabbing_spinner_attempt = false; diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h index 1bf8e8eef9..7e10764491 100644 --- a/editor/editor_spin_slider.h +++ b/editor/editor_spin_slider.h @@ -71,6 +71,7 @@ class EditorSpinSlider : public Range { void _value_input_closed(); void _value_input_submitted(const String &); void _value_focus_exited(); + void _value_input_gui_input(const Ref<InputEvent> &p_event); bool hide_slider; bool flat; diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 6e5b94dc07..2d4db48f2a 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -213,6 +213,7 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = exceptions.insert("EditorPivot"); exceptions.insert("EditorHandle"); exceptions.insert("Editor3DHandle"); + exceptions.insert("EditorBoneHandle"); exceptions.insert("Godot"); exceptions.insert("Sky"); exceptions.insert("EditorControlAnchor"); @@ -292,7 +293,8 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Ref<Theme> theme = Ref<Theme>(memnew(Theme)); - const float default_contrast = 0.3; + // Controls may rely on the scale for their internal drawing logic. + theme->set_default_theme_base_scale(EDSCALE); // Theme settings Color accent_color = EDITOR_GET("interface/theme/accent_color"); @@ -310,6 +312,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Color preset_base_color; float preset_contrast = 0; + const float default_contrast = 0.3; + // Please use alphabetical order if you're adding a new theme here // (after "Custom") @@ -962,8 +966,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("SceneTabFG", "EditorStyles", style_tab_selected); theme->set_stylebox("SceneTabBG", "EditorStyles", style_tab_unselected); theme->set_icon("close", "Tabs", theme->get_icon("GuiClose", "EditorIcons")); - theme->set_stylebox("button_pressed", "Tabs", style_menu); - theme->set_stylebox("button", "Tabs", style_menu); + theme->set_stylebox("close_bg_pressed", "Tabs", style_menu); + theme->set_stylebox("close_bg_highlight", "Tabs", style_menu); theme->set_icon("increment", "TabContainer", theme->get_icon("GuiScrollArrowRight", "EditorIcons")); theme->set_icon("decrement", "TabContainer", theme->get_icon("GuiScrollArrowLeft", "EditorIcons")); theme->set_icon("increment", "Tabs", theme->get_icon("GuiScrollArrowRight", "EditorIcons")); @@ -1084,6 +1088,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("folded", "CodeEdit", theme->get_icon("GuiTreeArrowRight", "EditorIcons")); theme->set_icon("can_fold", "CodeEdit", theme->get_icon("GuiTreeArrowDown", "EditorIcons")); theme->set_icon("executing_line", "CodeEdit", theme->get_icon("MainPlay", "EditorIcons")); + theme->set_icon("breakpoint", "CodeEdit", theme->get_icon("Breakpoint", "EditorIcons")); theme->set_constant("line_spacing", "CodeEdit", EDITOR_DEF("text_editor/appearance/whitespace/line_spacing", 6)); // H/VSplitContainer diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index b646b3361d..cb88e9d75e 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -743,7 +743,7 @@ void ExportTemplateManager::_notification(int p_what) { current_missing_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); current_installed_label->add_theme_color_override("font_color", get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); - mirror_options_button->set_icon(get_theme_icon(SNAME("GuiTabMenu"), SNAME("EditorIcons"))); + mirror_options_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); } break; case NOTIFICATION_VISIBILITY_CHANGED: { diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 5dd5c050e0..1d1976d7e5 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -106,7 +106,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory List<FileInfo> file_list; for (int i = 0; i < p_dir->get_file_count(); i++) { String file_type = p_dir->get_file_type(i); - if (_is_file_type_disabled_by_feature_profile(file_type)) { + if (file_type != "TextFile" && _is_file_type_disabled_by_feature_profile(file_type)) { // If type is disabled, file won't be displayed. continue; } @@ -1175,7 +1175,7 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ EditorData *ed = &editor->get_editor_data(); for (int j = 0; j < ed->get_edited_scene_count(); j++) { if (ed->get_scene_path(j) == file_changed_paths[i]) { - ed->get_edited_scene_root(j)->set_filename(new_item_path); + ed->get_edited_scene_root(j)->set_scene_file_path(new_item_path); editor->save_layout(); break; } @@ -1260,7 +1260,7 @@ void FileSystemDock::_update_resource_paths_after_move(const Map<String, String> continue; } - path = get_tree()->get_edited_scene_root()->get_filename(); + path = get_tree()->get_edited_scene_root()->get_scene_file_path(); } else { path = EditorNode::get_editor_data().get_scene_path(i); } @@ -1270,7 +1270,7 @@ void FileSystemDock::_update_resource_paths_after_move(const Map<String, String> } if (i == EditorNode::get_editor_data().get_edited_scene()) { - get_tree()->get_edited_scene_root()->set_filename(path); + get_tree()->get_edited_scene_root()->set_scene_file_path(path); } else { EditorNode::get_editor_data().set_scene_path(i, path); } @@ -1301,11 +1301,11 @@ void FileSystemDock::_update_dependencies_after_move(const Map<String, String> & void FileSystemDock::_update_project_settings_after_move(const Map<String, String> &p_renames) const { // Find all project settings of type FILE and replace them if needed. const Map<StringName, PropertyInfo> prop_info = ProjectSettings::get_singleton()->get_custom_property_info(); - for (const Map<StringName, PropertyInfo>::Element *E = prop_info.front(); E; E = E->next()) { - if (E->get().hint == PROPERTY_HINT_FILE) { - String old_path = GLOBAL_GET(E->key()); + for (const KeyValue<StringName, PropertyInfo> &E : prop_info) { + if (E.value.hint == PROPERTY_HINT_FILE) { + String old_path = GLOBAL_GET(E.key); if (p_renames.has(old_path)) { - ProjectSettings::get_singleton()->set_setting(E->key(), p_renames[old_path]); + ProjectSettings::get_singleton()->set_setting(E.key, p_renames[old_path]); } }; } @@ -1954,6 +1954,13 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected case FILE_NEW_RESOURCE: { new_resource_dialog->popup_create(true); } break; + case FILE_NEW_TEXTFILE: { + String fpath = path; + if (!fpath.ends_with("/")) { + fpath = fpath.get_base_dir(); + } + ScriptEditor::get_singleton()->open_text_file_create_dialog(fpath); + } break; } } @@ -2473,6 +2480,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str p_popup->add_icon_item(get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")), TTR("New Scene..."), FILE_NEW_SCENE); p_popup->add_icon_item(get_theme_icon(SNAME("Script"), SNAME("EditorIcons")), TTR("New Script..."), FILE_NEW_SCRIPT); p_popup->add_icon_item(get_theme_icon(SNAME("Object"), SNAME("EditorIcons")), TTR("New Resource..."), FILE_NEW_RESOURCE); + p_popup->add_icon_item(get_theme_icon(SNAME("TextFile"), SNAME("EditorIcons")), TTR("New TextFile..."), FILE_NEW_TEXTFILE); p_popup->add_separator(); } @@ -2513,6 +2521,7 @@ void FileSystemDock::_tree_rmb_empty(const Vector2 &p_pos) { tree_popup->add_icon_item(get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")), TTR("New Scene..."), FILE_NEW_SCENE); tree_popup->add_icon_item(get_theme_icon(SNAME("Script"), SNAME("EditorIcons")), TTR("New Script..."), FILE_NEW_SCRIPT); tree_popup->add_icon_item(get_theme_icon(SNAME("Object"), SNAME("EditorIcons")), TTR("New Resource..."), FILE_NEW_RESOURCE); + tree_popup->add_icon_item(get_theme_icon(SNAME("TextFile"), SNAME("EditorIcons")), TTR("New TextFile..."), FILE_NEW_TEXTFILE); tree_popup->set_position(tree->get_global_position() + p_pos); tree_popup->popup(); } @@ -2558,6 +2567,7 @@ void FileSystemDock::_file_list_rmb_pressed(const Vector2 &p_pos) { file_list_popup->add_icon_item(get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")), TTR("New Scene..."), FILE_NEW_SCENE); file_list_popup->add_icon_item(get_theme_icon(SNAME("Script"), SNAME("EditorIcons")), TTR("New Script..."), FILE_NEW_SCRIPT); file_list_popup->add_icon_item(get_theme_icon(SNAME("Object"), SNAME("EditorIcons")), TTR("New Resource..."), FILE_NEW_RESOURCE); + file_list_popup->add_icon_item(get_theme_icon(SNAME("TextFile"), SNAME("EditorIcons")), TTR("New TextFile..."), FILE_NEW_TEXTFILE); file_list_popup->add_separator(); file_list_popup->add_icon_item(get_theme_icon(SNAME("Filesystem"), SNAME("EditorIcons")), TTR("Open in File Manager"), FILE_SHOW_IN_EXPLORER); file_list_popup->set_position(files->get_global_position() + p_pos); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 21a7abe622..73bdd685b7 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -101,6 +101,7 @@ private: FILE_SHOW_IN_EXPLORER, FILE_COPY_PATH, FILE_NEW_RESOURCE, + FILE_NEW_TEXTFILE, FOLDER_EXPAND_ALL, FOLDER_COLLAPSE_ALL, }; diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 9444706fd2..b61f6e12eb 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -233,8 +233,9 @@ void FindInFiles::_scan_dir(String path, PackedStringArray &out_folders) { break; } - // Ignore special dirs (such as .git and .import) - if (file == "." || file == ".." || file.begins_with(".")) { + // 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.begins_with(project_data_dir_name)) { continue; } if (dir->current_is_hidden()) { @@ -840,8 +841,8 @@ void FindInFilesPanel::_on_replace_all_clicked() { PackedStringArray modified_files; - for (Map<String, TreeItem *>::Element *E = _file_items.front(); E; E = E->next()) { - TreeItem *file_item = E->value(); + for (KeyValue<String, TreeItem *> &E : _file_items) { + TreeItem *file_item = E.value; String fpath = file_item->get_metadata(0); Vector<Result> locations; diff --git a/editor/icons/Breakpoint.svg b/editor/icons/Breakpoint.svg new file mode 100644 index 0000000000..b95c2b511e --- /dev/null +++ b/editor/icons/Breakpoint.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#e1e1e1"/><pathd="m12 8c0 2.209-1.791 4-4 4s-4-1.791-4-4 1.791-4 4-4 4 1.791 4 4" fill="#f6f6f6"/></svg> diff --git a/editor/icons/EditorBoneHandle.svg b/editor/icons/EditorBoneHandle.svg new file mode 100644 index 0000000000..a6d7c3f878 --- /dev/null +++ b/editor/icons/EditorBoneHandle.svg @@ -0,0 +1 @@ +<svg height="8" viewBox="0 0 8 8" width="8" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="4" fill="#fff" r="4"/><circle cx="4" cy="4" fill="#000" r="2.5"/></svg> diff --git a/editor/icons/KeyBlendShape.svg b/editor/icons/KeyBlendShape.svg new file mode 100644 index 0000000000..42f7341942 --- /dev/null +++ b/editor/icons/KeyBlendShape.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="svg80" + sodipodi:docname="KeyBlendShape.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="defs84" /> + <sodipodi:namedview + id="namedview82" + 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.6599526" + 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="svg80" /> + <rect + fill="#3cf34e" + height="6.1027" + ry=".76286" + transform="matrix(.70710678 -.70710678 .70710678 .70710678 0 -1042.4)" + width="6.1027" + x="-740.13947" + y="741.10779" + id="rect78" + style="fill:#5ad5c4;fill-opacity:1" /> +</svg> diff --git a/editor/icons/KeyTrackBlendShape.svg b/editor/icons/KeyTrackBlendShape.svg new file mode 100644 index 0000000000..e82f0d6a6f --- /dev/null +++ b/editor/icons/KeyTrackBlendShape.svg @@ -0,0 +1,45 @@ +<?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="KeyTrackBlendShape.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="29.839906" + inkscape:cx="-3.5690461" + inkscape:cy="9.0985541" + 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 + id="path1910" + style="color:#000000;fill:#5ad5c4;stroke-linejoin:round;-inkscape-stroke:none" + d="m 4.5230825,1.1341567 c -2.1310744,0.017055 -3.86718737,1.7635044 -3.86718737,3.8984375 0,1.8778511 1.34348597,3.4523891 3.11718737,3.8164061 L 3.95277,7.5794693 C 2.7929991,7.3095662 1.9351921,6.2780435 1.9351921,5.0325942 c 0,-1.4262775 1.123493,-2.5732858 2.5390622,-2.6152344 v 0.017578 h 0.2011719 l 0.1796875,-1.28125 H 4.5230825 v -0.011719 c 0,-0.00263 -2.64e-5,-0.00518 0,-0.00781 z m 1.6816406,0.019531 -0.1796875,1.28125 h 1.3085937 c 0.078866,0 0.1230469,0.044181 0.1230469,0.1230469 v 4.9882815 c 0,0.07887 -0.044181,0.121093 -0.1230469,0.121094 H 5.2887075 L 5.10902,8.9486103 h 2.2246093 c 0.7663818,0 1.4042969,-0.635962 1.4042969,-1.402344 V 2.5579848 c 0,-0.7663818 -0.637915,-1.4042969 -1.4042969,-1.4042969 z" /> +</svg> 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/icons/ToolBoneSelect.svg b/editor/icons/ToolBoneSelect.svg new file mode 100644 index 0000000000..cc12b69a82 --- /dev/null +++ b/editor/icons/ToolBoneSelect.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m16 11.46-6.142-2.527-1.572-.647.647 1.572 2.527 6.142.913-2.72 1.815 1.817.91-.909-1.818-1.815z"/><path d="m7.784 11.008-.886-2.152c-.23-.56-.102-1.203.327-1.631.287-.287.67-.439 1.061-.439.192 0 .386.037.57.113l2.151.885.17-.17c.977.645 2.271.516 3.1-.311.964-.963.964-2.524 0-3.488-.377-.377-.867-.622-1.396-.697-.074-.529-.318-1.019-.695-1.397-.455-.453-1.067-.711-1.707-.721-.667-.01-1.309.25-1.782.72-.828.829-.96 2.126-.314 3.105l-3.558 3.561c-.978-.646-2.274-.515-3.103.312-.963.962-.963 2.524 0 3.487.378.377.868.621 1.396.695.075.529.319 1.02.696 1.396.963.964 2.525.964 3.488 0 .828-.828.96-2.125.314-3.104z"/></g></svg> diff --git a/editor/import/collada.cpp b/editor/import/collada.cpp index b6d0927ce6..4cd9066350 100644 --- a/editor/import/collada.cpp +++ b/editor/import/collada.cpp @@ -2095,19 +2095,19 @@ void Collada::_merge_skeletons(VisualScene *p_vscene, Node *p_node) { } void Collada::_merge_skeletons2(VisualScene *p_vscene) { - for (Map<String, SkinControllerData>::Element *E = state.skin_controller_data_map.front(); E; E = E->next()) { - SkinControllerData &cd = E->get(); + for (KeyValue<String, SkinControllerData> &E : state.skin_controller_data_map) { + SkinControllerData &cd = E.value; NodeSkeleton *skeleton = nullptr; - for (Map<String, Transform3D>::Element *F = cd.bone_rest_map.front(); F; F = F->next()) { + for (const KeyValue<String, Transform3D> &F : cd.bone_rest_map) { String name; - if (!state.sid_to_node_map.has(F->key())) { + if (!state.sid_to_node_map.has(F.key)) { continue; } - name = state.sid_to_node_map[F->key()]; + name = state.sid_to_node_map[F.key]; ERR_CONTINUE(!state.scene_map.has(name)); @@ -2248,9 +2248,9 @@ bool Collada::_move_geometry_to_skeletons(VisualScene *p_vscene, Node *p_node, L p_node->default_transform = skel_inv * (skin.bind_shape /* p_node->get_global_transform()*/); // i honestly have no idea what to do with a previous model xform.. most exporters ignore it //make rests relative to the skeleton (they seem to be always relative to world) - for (Map<String, Transform3D>::Element *E = skin.bone_rest_map.front(); E; E = E->next()) { - E->get() = skel_inv * E->get(); //make the bone rest local to the skeleton - state.bone_rest_map[E->key()] = E->get(); // make it remember where the bone is globally, now that it's relative + for (KeyValue<String, Transform3D> &E : skin.bone_rest_map) { + E.value = skel_inv * E.value; //make the bone rest local to the skeleton + state.bone_rest_map[E.key] = E.value; // make it remember where the bone is globally, now that it's relative } //but most exporters seem to work only if i do this.. @@ -2302,8 +2302,8 @@ void Collada::_find_morph_nodes(VisualScene *p_vscene, Node *p_node) { } void Collada::_optimize() { - for (Map<String, VisualScene>::Element *E = state.visual_scene_map.front(); E; E = E->next()) { - VisualScene &vs = E->get(); + for (KeyValue<String, VisualScene> &E : state.visual_scene_map) { + VisualScene &vs = E.value; for (int i = 0; i < vs.root_nodes.size(); i++) { _create_skeletons(&vs.root_nodes.write[i]); } diff --git a/editor/import/dynamicfont_import_settings.cpp b/editor/import/dynamicfont_import_settings.cpp index 9a8abfa5c6..474c9d5296 100644 --- a/editor/import/dynamicfont_import_settings.cpp +++ b/editor/import/dynamicfont_import_settings.cpp @@ -1011,9 +1011,10 @@ void DynamicFontImportSettings::_glyph_text_selected() { if (text_rid.is_valid()) { TS->shaped_text_add_string(text_rid, text_edit->get_text(), font_main->get_rids(), 16, ftrs, text_edit->get_language()); TS->shaped_text_shape(text_rid); - const Vector<TextServer::Glyph> &gl = TS->shaped_text_get_glyphs(text_rid); + const Glyph *gl = TS->shaped_text_get_glyphs(text_rid); + const int gl_size = TS->shaped_text_get_glyph_count(text_rid); - for (int i = 0; i < gl.size(); i++) { + for (int i = 0; i < gl_size; i++) { if (gl[i].font_rid.is_valid() && gl[i].index != 0) { selected_glyphs.insert(gl[i].index); } diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 7ab80ac3b4..ac8adedf2f 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -33,8 +33,8 @@ #include "core/os/os.h" #include "editor/editor_node.h" #include "editor/import/collada.h" -#include "editor/import/scene_importer_mesh_node_3d.h" #include "scene/3d/camera_3d.h" +#include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/node_3d.h" @@ -42,6 +42,7 @@ #include "scene/3d/skeleton_3d.h" #include "scene/animation/animation_player.h" #include "scene/resources/animation.h" +#include "scene/resources/importer_mesh.h" #include "scene/resources/packed_scene.h" #include "scene/resources/surface_tool.h" @@ -68,7 +69,7 @@ struct ColladaImport { Map<String, NodeMap> node_map; //map from collada node to engine node Map<String, String> node_name_map; //map from collada node to engine node - Map<String, Ref<EditorSceneImporterMesh>> mesh_cache; + Map<String, Ref<ImporterMesh>> mesh_cache; Map<String, Ref<Curve3D>> curve_cache; Map<String, Ref<Material>> material_cache; Map<Collada::Node *, Skeleton3D *> skeleton_map; @@ -87,11 +88,11 @@ struct ColladaImport { Error _create_scene(Collada::Node *p_node, Node3D *p_parent); Error _create_resources(Collada::Node *p_node, bool p_use_compression); Error _create_material(const String &p_target); - Error _create_mesh_surfaces(bool p_optimize, Ref<EditorSceneImporterMesh> &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<EditorSceneImporterMesh>> p_morph_meshes = Vector<Ref<EditorSceneImporterMesh>>(), bool p_use_compression = false, bool p_use_mesh_material = false); + 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; @@ -119,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); + xform = 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? @@ -282,8 +292,8 @@ Error ColladaImport::_create_scene(Collada::Node *p_node, Node3D *p_parent) { node = memnew(Path3D); } else { //mesh since nothing else - node = memnew(EditorSceneImporterMeshNode3D); - //Object::cast_to<EditorSceneImporterMeshNode3D>(node)->set_flag(GeometryInstance3D::FLAG_USE_BAKED_LIGHT, true); + node = memnew(ImporterMeshInstance3D); + //Object::cast_to<ImporterMeshInstance3D>(node)->set_flag(GeometryInstance3D::FLAG_USE_BAKED_LIGHT, true); } } break; case Collada::Node::TYPE_SKELETON: { @@ -457,7 +467,7 @@ Error ColladaImport::_create_material(const String &p_target) { return OK; } -Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<EditorSceneImporterMesh> &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<EditorSceneImporterMesh>> p_morph_meshes, bool p_use_compression, bool p_use_mesh_material) { +Error ColladaImport::_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, bool p_use_compression, bool p_use_mesh_material) { bool local_xform_mirror = p_local_xform.basis.determinant() < 0; if (p_morph_data) { @@ -1087,10 +1097,10 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres } } - if (Object::cast_to<EditorSceneImporterMeshNode3D>(node)) { + if (Object::cast_to<ImporterMeshInstance3D>(node)) { Collada::NodeGeometry *ng2 = static_cast<Collada::NodeGeometry *>(p_node); - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(node); + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(node); ERR_FAIL_COND_V(!mi, ERR_BUG); @@ -1099,7 +1109,7 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres String meshid; Transform3D apply_xform; Vector<int> bone_remap; - Vector<Ref<EditorSceneImporterMesh>> morphs; + Vector<Ref<ImporterMesh>> morphs; if (ng2->controller) { String ngsource = ng2->source; @@ -1168,10 +1178,10 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres for (int i = 0; i < names.size(); i++) { String meshid2 = names[i]; if (collada.state.mesh_data_map.has(meshid2)) { - Ref<EditorSceneImporterMesh> mesh = Ref<EditorSceneImporterMesh>(memnew(EditorSceneImporterMesh)); + Ref<ImporterMesh> mesh = Ref<ImporterMesh>(memnew(ImporterMesh)); const Collada::MeshData &meshdata = collada.state.mesh_data_map[meshid2]; mesh->set_name(meshdata.name); - Error err = _create_mesh_surfaces(false, mesh, ng2->material_map, meshdata, apply_xform, bone_remap, skin, nullptr, Vector<Ref<EditorSceneImporterMesh>>(), false); + Error err = _create_mesh_surfaces(false, mesh, ng2->material_map, meshdata, apply_xform, bone_remap, skin, nullptr, Vector<Ref<ImporterMesh>>(), false); ERR_FAIL_COND_V(err, err); morphs.push_back(mesh); @@ -1194,7 +1204,7 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres meshid = ng2->source; } - Ref<EditorSceneImporterMesh> mesh; + Ref<ImporterMesh> mesh; if (mesh_cache.has(meshid)) { mesh = mesh_cache[meshid]; } else { @@ -1202,7 +1212,7 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres //bleh, must ignore invalid ERR_FAIL_COND_V(!collada.state.mesh_data_map.has(meshid), ERR_INVALID_DATA); - mesh = Ref<EditorSceneImporterMesh>(memnew(EditorSceneImporterMesh)); + mesh = Ref<ImporterMesh>(memnew(ImporterMesh)); const Collada::MeshData &meshdata = collada.state.mesh_data_map[meshid]; String name = meshdata.name; if (name == "") { @@ -1314,8 +1324,8 @@ Error ColladaImport::load(const String &p_path, int p_flags, bool p_force_make_t } void ColladaImport::_fix_param_animation_tracks() { - for (Map<String, Collada::Node *>::Element *E = collada.state.scene_map.front(); E; E = E->next()) { - Collada::Node *n = E->get(); + for (KeyValue<String, Collada::Node *> &E : collada.state.scene_map) { + Collada::Node *n = E.value; switch (n->type) { case Collada::Node::TYPE_NODE: { // ? do nothing @@ -1363,7 +1373,7 @@ void ColladaImport::_fix_param_animation_tracks() { for (int rti = 0; rti < rt.size(); rti++) { Collada::AnimationTrack *at = &collada.state.animation_tracks.write[rt[rti]]; - at->target = E->key(); + at->target = E.key; at->param = "morph/" + collada.state.mesh_name_map[mesh_name]; at->property = true; //at->param @@ -1383,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++) { @@ -1416,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) { @@ -1431,11 +1441,11 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones animation->set_name(collada.state.animation_clips[p_clip].name); } - for (Map<String, NodeMap>::Element *E = node_map.front(); E; E = E->next()) { - if (E->get().bone < 0) { + for (const KeyValue<String, NodeMap> &E : node_map) { + if (E.value.bone < 0) { continue; } - bones_with_animation[E->key()] = false; + bones_with_animation[E.key] = false; } //store and validate tracks @@ -1521,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; @@ -1593,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) { @@ -1620,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 (Map<String, bool>::Element *E = bones_with_animation.front(); E; E = E->next()) { - if (E->get()) { - 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; } } @@ -1689,7 +1710,7 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones NodeMap &nm = node_map[at.target]; String path = scene->get_path_to(nm.node); - animation->add_track(Animation::TYPE_VALUE); + animation->add_track(Animation::TYPE_BLEND_SHAPE); int track = animation->get_track_count() - 1; path = path + ":" + at.param; @@ -1711,7 +1732,7 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones WARN_PRINT("Collada: Unexpected amount of value keys: " + itos(data.size())); } - animation->track_insert_key(track, time, value); + animation->blend_shape_track_insert_key(track, time, value); } tracks_found = true; @@ -1727,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; } @@ -1748,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; @@ -1772,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; @@ -1791,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); } @@ -1812,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 00dce6850e..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 "editor/import/scene_importer_mesh_node_3d.h" -#include "resource_importer_scene.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(); - EditorSceneImporterMeshNode3D *editor_mesh_3d = scene->cast_to<EditorSceneImporterMeshNode3D>(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 (Map<StringName, BakeResetRestBone>::Element *rest_bone_i = r_rest_bones.front(); rest_bone_i; rest_bone_i = rest_bone_i->next()) { - 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()); - BakeResetRestBone rest_bone = rest_bone_i->get(); - 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.cpp b/editor/import/resource_importer_obj.cpp index 34bc0a7d8d..4f75faedcb 100644 --- a/editor/import/resource_importer_obj.cpp +++ b/editor/import/resource_importer_obj.cpp @@ -32,10 +32,10 @@ #include "core/io/file_access.h" #include "core/io/resource_saver.h" -#include "editor/import/scene_importer_mesh.h" -#include "editor/import/scene_importer_mesh_node_3d.h" +#include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/node_3d.h" +#include "scene/resources/importer_mesh.h" #include "scene/resources/mesh.h" #include "scene/resources/surface_tool.h" @@ -439,13 +439,13 @@ Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, in Node3D *scene = memnew(Node3D); for (const Ref<Mesh> &m : meshes) { - Ref<EditorSceneImporterMesh> mesh; + Ref<ImporterMesh> mesh; mesh.instantiate(); for (int i = 0; i < m->get_surface_count(); i++) { mesh->add_surface(m->surface_get_primitive_type(i), m->surface_get_arrays(i), Array(), Dictionary(), m->surface_get_material(i)); } - EditorSceneImporterMeshNode3D *mi = memnew(EditorSceneImporterMeshNode3D); + ImporterMeshInstance3D *mi = memnew(ImporterMeshInstance3D); mi->set_mesh(mesh); mi->set_name(m->get_name()); scene->add_child(mi); 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 c48d9bb117..319e5ee25f 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -32,11 +32,11 @@ #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 "editor/import/scene_importer_mesh_node_3d.h" #include "scene/3d/area_3d.h" #include "scene/3d/collision_shape_3d.h" +#include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/physics_body_3d.h" @@ -44,6 +44,7 @@ #include "scene/animation/animation_player.h" #include "scene/resources/animation.h" #include "scene/resources/box_shape_3d.h" +#include "scene/resources/importer_mesh.h" #include "scene/resources/packed_scene.h" #include "scene/resources/resource_format_text.h" #include "scene/resources/separation_ray_shape_3d.h" @@ -51,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; @@ -60,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++) { @@ -72,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); @@ -81,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; @@ -93,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); @@ -143,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"; } @@ -152,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); } } @@ -180,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; } @@ -233,7 +340,7 @@ static String _fixstr(const String &p_what, const String &p_str) { return what; } -static void _pre_gen_shape_list(Ref<EditorSceneImporterMesh> &mesh, Vector<Ref<Shape3D>> &r_shape_list, bool p_convex) { +static void _pre_gen_shape_list(Ref<ImporterMesh> &mesh, Vector<Ref<Shape3D>> &r_shape_list, bool p_convex) { ERR_FAIL_NULL_MSG(mesh, "Cannot generate shape list with null mesh value"); if (!p_convex) { Ref<Shape3D> shape = mesh->create_trimesh_shape(); @@ -249,7 +356,7 @@ static void _pre_gen_shape_list(Ref<EditorSceneImporterMesh> &mesh, Vector<Ref<S } } -Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map) { +Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map) { // children first for (int i = 0; i < p_node->get_child_count(); i++) { Node *r = _pre_fix_node(p_node->get_child(i), p_root, collision_map); @@ -267,10 +374,10 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E return nullptr; } - if (Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + if (Object::cast_to<ImporterMeshInstance3D>(p_node)) { + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); - Ref<EditorSceneImporterMesh> m = mi->get_mesh(); + Ref<ImporterMesh> m = mi->get_mesh(); if (m.is_valid()) { for (int i = 0; i < m->get_surface_count(); i++) { @@ -331,9 +438,9 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E if (isroot) { return p_node; } - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); if (mi) { - Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); + Ref<ImporterMesh> mesh = mi->get_mesh(); if (mesh.is_valid()) { Vector<Ref<Shape3D>> shapes; @@ -398,13 +505,13 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E colshape->set_owner(sb->get_owner()); } - } else if (_teststr(name, "rigid") && Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { + } else if (_teststr(name, "rigid") && Object::cast_to<ImporterMeshInstance3D>(p_node)) { if (isroot) { return p_node; } - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); - Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); + Ref<ImporterMesh> mesh = mi->get_mesh(); if (mesh.is_valid()) { Vector<Ref<Shape3D>> shapes; @@ -426,10 +533,10 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E _add_shapes(rigid_body, shapes); } - } else if ((_teststr(name, "col") || (_teststr(name, "convcol"))) && Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + } else if ((_teststr(name, "col") || (_teststr(name, "convcol"))) && Object::cast_to<ImporterMeshInstance3D>(p_node)) { + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); - Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); + Ref<ImporterMesh> mesh = mi->get_mesh(); if (mesh.is_valid()) { Vector<Ref<Shape3D>> shapes; @@ -465,14 +572,14 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E } } - } else if (_teststr(name, "navmesh") && Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { + } else if (_teststr(name, "navmesh") && Object::cast_to<ImporterMeshInstance3D>(p_node)) { if (isroot) { return p_node; } - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); - Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); + Ref<ImporterMesh> mesh = mi->get_mesh(); ERR_FAIL_COND_V(mesh.is_null(), nullptr); NavigationRegion3D *nmi = memnew(NavigationRegion3D); @@ -484,12 +591,12 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E memdelete(p_node); p_node = nmi; - } else if (Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { + } else if (Object::cast_to<ImporterMeshInstance3D>(p_node)) { //last attempt, maybe collision inside the mesh data - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); - Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); + Ref<ImporterMesh> mesh = mi->get_mesh(); if (!mesh.is_null()) { Vector<Ref<Shape3D>> shapes; if (collision_map.has(mesh)) { @@ -517,7 +624,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E return p_node; } -Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps) { +Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps) { // children first for (int i = 0; i < p_node->get_child_count(); i++) { Node *r = _post_fix_node(p_node->get_child(i), p_root, collision_map, r_scanned_meshes, p_node_data, p_material_data, p_animation_data, p_animation_fps); @@ -546,10 +653,30 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< return nullptr; } - if (Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(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_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; + } + } + } - Ref<EditorSceneImporterMesh> m = mi->get_mesh(); + if (Object::cast_to<ImporterMeshInstance3D>(p_node)) { + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); + + Ref<ImporterMesh> m = mi->get_mesh(); if (m.is_valid()) { if (!r_scanned_meshes.has(m)) { @@ -565,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); @@ -669,10 +801,10 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< } //navmesh (node may have changed type above) - if (Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { - EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + if (Object::cast_to<ImporterMeshInstance3D>(p_node)) { + ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); - Ref<EditorSceneImporterMesh> m = mi->get_mesh(); + Ref<ImporterMesh> m = mi->get_mesh(); if (m.is_valid()) { if (node_settings.has("generate/navmesh")) { @@ -714,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"]; @@ -778,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); + } + } + } } } @@ -850,44 +1021,71 @@ 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); + } else if (default_anim->track_get_type(j) == Animation::TYPE_BLEND_SHAPE) { + float interp; + default_anim->blend_shape_track_interpolate(j, from, &interp); + new_anim->blend_shape_track_insert_key(dtrack, 0, interp); } } } - 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); + } else if (default_anim->track_get_type(j) == Animation::TYPE_BLEND_SHAPE) { + float interp; + default_anim->blend_shape_track_get_key(j, k, &interp); + new_anim->blend_shape_track_insert_key(dtrack, kt - from, interp); } } 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); + } else if (default_anim->track_get_type(j) == Animation::TYPE_BLEND_SHAPE) { + float interp; + default_anim->blend_shape_track_interpolate(j, to, &interp); + new_anim->blend_shape_track_insert_key(dtrack, to - from, interp); } } } @@ -896,20 +1094,35 @@ 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); new_anim->track_insert_key(dtrack, to - from, to_var); + } else if (default_anim->track_get_type(j) == Animation::TYPE_BLEND_SHAPE) { + float interp; + default_anim->blend_shape_track_interpolate(j, from, &interp); + new_anim->blend_shape_track_insert_key(dtrack, 0, interp); + default_anim->blend_shape_track_interpolate(j, to, &interp); + new_anim->blend_shape_track_insert_key(dtrack, to - from, interp); } } } @@ -980,6 +1193,8 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/shadow_meshes", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lightmap_uv", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lods", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_split_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 25.0f)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_merge_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 60.0f)); } break; case INTERNAL_IMPORT_CATEGORY_MATERIAL: { r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "use_external/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); @@ -997,6 +1212,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++) { @@ -1012,6 +1230,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 { @@ -1114,6 +1336,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; } @@ -1141,6 +1370,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; } @@ -1168,11 +1405,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) { @@ -1186,11 +1426,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; } @@ -1216,11 +1456,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; } @@ -1245,7 +1485,7 @@ Ref<Animation> ResourceImporterScene::import_animation_from_other_importer(Edito } void ResourceImporterScene::_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) { - EditorSceneImporterMeshNode3D *src_mesh_node = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + ImporterMeshInstance3D *src_mesh_node = Object::cast_to<ImporterMeshInstance3D>(p_node); if (src_mesh_node) { //is mesh MeshInstance3D *mesh_node = memnew(MeshInstance3D); @@ -1259,6 +1499,8 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m //do mesh processing bool generate_lods = p_generate_lods; + float split_angle = 25.0f; + float merge_angle = 60.0f; bool create_shadow_meshes = p_create_shadow_meshes; bool bake_lightmaps = p_light_bake_mode == LIGHT_BAKE_STATIC_LIGHTMAPS; String save_to_file; @@ -1301,17 +1543,30 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m } } + if (mesh_settings.has("lods/normal_split_angle")) { + split_angle = mesh_settings["lods/normal_split_angle"]; + } + + if (mesh_settings.has("lods/normal_merge_angle")) { + merge_angle = mesh_settings["lods/normal_merge_angle"]; + } + if (mesh_settings.has("save_to_file/enabled") && bool(mesh_settings["save_to_file/enabled"]) && mesh_settings.has("save_to_file/path")) { save_to_file = mesh_settings["save_to_file/path"]; if (!save_to_file.is_resource_file()) { 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) { - src_mesh_node->get_mesh()->generate_lods(); + src_mesh_node->get_mesh()->generate_lods(merge_angle, split_angle); } + if (create_shadow_meshes) { src_mesh_node->get_mesh()->create_shadow_mesh(); } @@ -1408,14 +1663,164 @@ 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, Animation::TYPE_BLEND_SHAPE }; + 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); + + if (j == TRACK_CHANNEL_BLEND_SHAPE) { + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(n); + if (mi && path.get_subname_count() > 0) { + StringName bs = path.get_subname(0); + bool valid; + float value = mi->get(bs, &valid); + if (valid) { + int track_idx = anim->add_track(track_types[j]); + anim->track_set_path(track_idx, path); + anim->track_set_imported(track_idx, true); + anim->blend_shape_track_insert_key(track_idx, 0, value); + } + } + + } else { + 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); @@ -1434,12 +1839,12 @@ 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; } - Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> collision_map; + Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> collision_map; _pre_fix_node(scene, scene, collision_map); @@ -1449,13 +1854,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); @@ -1478,16 +1883,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; @@ -1514,17 +1919,17 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p animation_data = subresources["animations"]; } - Set<Ref<EditorSceneImporterMesh>> scanned_meshes; - Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> collision_map; + Set<Ref<ImporterMesh>> scanned_meshes; + 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. @@ -1626,6 +2031,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); @@ -1657,15 +2066,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 + "'."); @@ -1676,6 +2085,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 e232b715be..e1e7046be5 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -41,9 +41,9 @@ class Material; class AnimationPlayer; -class EditorSceneImporterMesh; -class EditorSceneImporter : public RefCounted { - GDCLASS(EditorSceneImporter, RefCounted); +class ImporterMesh; +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,33 @@ 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_BLEND_SHAPE, + 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 +237,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; @@ -181,8 +255,8 @@ public: // Import scenes *after* everything else (such as textures). virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; } - Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map); - Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps); + Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map); + Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps); Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks); void _create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all); @@ -191,8 +265,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 +282,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/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index 61745cb6ee..96a53b3257 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -86,28 +86,28 @@ void ResourceImporterTexture::update_imports() { return; } - for (Map<StringName, MakeInfo>::Element *E = make_flags.front(); E; E = E->next()) { + for (const KeyValue<StringName, MakeInfo> &E : make_flags) { Ref<ConfigFile> cf; cf.instantiate(); - String src_path = String(E->key()) + ".import"; + String src_path = String(E.key) + ".import"; Error err = cf->load(src_path); ERR_CONTINUE(err != OK); bool changed = false; - if (E->get().flags & MAKE_NORMAL_FLAG && int(cf->get_value("params", "compress/normal_map")) == 0) { + if (E.value.flags & MAKE_NORMAL_FLAG && int(cf->get_value("params", "compress/normal_map")) == 0) { cf->set_value("params", "compress/normal_map", 1); changed = true; } - if (E->get().flags & MAKE_ROUGHNESS_FLAG && int(cf->get_value("params", "roughness/mode")) == 0) { - cf->set_value("params", "roughness/mode", E->get().channel_for_roughness + 2); - cf->set_value("params", "roughness/src_normal", E->get().normal_path_for_roughness); + if (E.value.flags & MAKE_ROUGHNESS_FLAG && int(cf->get_value("params", "roughness/mode")) == 0) { + cf->set_value("params", "roughness/mode", E.value.channel_for_roughness + 2); + cf->set_value("params", "roughness/src_normal", E.value.normal_path_for_roughness); changed = true; } - if (E->get().flags & MAKE_3D_FLAG && bool(cf->get_value("params", "detect_3d/compress_to"))) { + if (E.value.flags & MAKE_3D_FLAG && bool(cf->get_value("params", "detect_3d/compress_to"))) { int compress_to = cf->get_value("params", "detect_3d/compress_to"); cf->set_value("params", "detect_3d/compress_to", 0); if (compress_to == 1) { @@ -121,7 +121,7 @@ void ResourceImporterTexture::update_imports() { if (changed) { cf->save(src_path); - to_reimport.push_back(E->key()); + to_reimport.push_back(E.key); } } diff --git a/editor/import/resource_importer_texture_atlas.cpp b/editor/import/resource_importer_texture_atlas.cpp index 36fd161c35..cf699599ae 100644 --- a/editor/import/resource_importer_texture_atlas.cpp +++ b/editor/import/resource_importer_texture_atlas.cpp @@ -74,6 +74,7 @@ String ResourceImporterTextureAtlas::get_preset_name(int p_idx) const { void ResourceImporterTextureAtlas::get_import_options(List<ImportOption> *r_options, int p_preset) const { r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "atlas_file", PROPERTY_HINT_SAVE_FILE, "*.png"), "")); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_mode", PROPERTY_HINT_ENUM, "Region,Mesh2D"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "crop_to_region"), false)); } String ResourceImporterTextureAtlas::get_option_group_file() const { @@ -206,6 +207,7 @@ Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file ERR_CONTINUE(err != OK); pack_data.image = image; + pack_data.is_cropped = options["crop_to_region"]; int mode = options["import_mode"]; @@ -324,7 +326,10 @@ Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file atlas_texture.instantiate(); atlas_texture->set_atlas(cache); atlas_texture->set_region(Rect2(offset, pack_data.region.size)); - atlas_texture->set_margin(Rect2(pack_data.region.position, pack_data.image->get_size() - pack_data.region.size)); + + if (!pack_data.is_cropped) { + atlas_texture->set_margin(Rect2(pack_data.region.position, pack_data.image->get_size() - pack_data.region.size)); + } texture = atlas_texture; } else { diff --git a/editor/import/resource_importer_texture_atlas.h b/editor/import/resource_importer_texture_atlas.h index b675d12477..d518a120bf 100644 --- a/editor/import/resource_importer_texture_atlas.h +++ b/editor/import/resource_importer_texture_atlas.h @@ -38,6 +38,7 @@ class ResourceImporterTextureAtlas : public ResourceImporter { struct PackData { Rect2 region; + bool is_cropped = false; bool is_mesh = false; Vector<int> chart_pieces; //one for region, many for mesh Vector<Vector<Vector2>> chart_vertices; //for mesh diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp index f99ab9888a..95a96f9e26 100644 --- a/editor/import/scene_import_settings.cpp +++ b/editor/import/scene_import_settings.cpp @@ -31,7 +31,8 @@ #include "scene_import_settings.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" -#include "editor/import/scene_importer_mesh_node_3d.h" +#include "scene/3d/importer_mesh_instance_3d.h" +#include "scene/resources/importer_mesh.h" #include "scene/resources/surface_tool.h" class SceneImportSettingsData : public Object { @@ -240,7 +241,7 @@ void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) { p_node->set_meta("import_id", import_id); } - EditorSceneImporterMeshNode3D *src_mesh_node = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + ImporterMeshInstance3D *src_mesh_node = Object::cast_to<ImporterMeshInstance3D>(p_node); if (src_mesh_node) { MeshInstance3D *mesh_node = memnew(MeshInstance3D); @@ -249,7 +250,7 @@ void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) { mesh_node->set_skin(src_mesh_node->get_skin()); mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path()); if (src_mesh_node->get_mesh().is_valid()) { - Ref<EditorSceneImporterMesh> editor_mesh = src_mesh_node->get_mesh(); + Ref<ImporterMesh> editor_mesh = src_mesh_node->get_mesh(); mesh_node->set_mesh(editor_mesh->get_mesh()); } @@ -771,52 +772,52 @@ void SceneImportSettings::_re_import() { Dictionary subresources; - for (Map<String, NodeData>::Element *E = node_map.front(); E; E = E->next()) { - if (E->get().settings.size()) { + for (KeyValue<String, NodeData> &E : node_map) { + if (E.value.settings.size()) { Dictionary d; - for (Map<StringName, Variant>::Element *F = E->get().settings.front(); F; F = F->next()) { - d[String(F->key())] = F->get(); + for (const KeyValue<StringName, Variant> &F : E.value.settings) { + d[String(F.key)] = F.value; } - nodes[E->key()] = d; + nodes[E.key] = d; } } if (nodes.size()) { subresources["nodes"] = nodes; } - for (Map<String, MaterialData>::Element *E = material_map.front(); E; E = E->next()) { - if (E->get().settings.size()) { + for (KeyValue<String, MaterialData> &E : material_map) { + if (E.value.settings.size()) { Dictionary d; - for (Map<StringName, Variant>::Element *F = E->get().settings.front(); F; F = F->next()) { - d[String(F->key())] = F->get(); + for (const KeyValue<StringName, Variant> &F : E.value.settings) { + d[String(F.key)] = F.value; } - materials[E->key()] = d; + materials[E.key] = d; } } if (materials.size()) { subresources["materials"] = materials; } - for (Map<String, MeshData>::Element *E = mesh_map.front(); E; E = E->next()) { - if (E->get().settings.size()) { + for (KeyValue<String, MeshData> &E : mesh_map) { + if (E.value.settings.size()) { Dictionary d; - for (Map<StringName, Variant>::Element *F = E->get().settings.front(); F; F = F->next()) { - d[String(F->key())] = F->get(); + for (const KeyValue<StringName, Variant> &F : E.value.settings) { + d[String(F.key)] = F.value; } - meshes[E->key()] = d; + meshes[E.key] = d; } } if (meshes.size()) { subresources["meshes"] = meshes; } - for (Map<String, AnimationData>::Element *E = animation_map.front(); E; E = E->next()) { - if (E->get().settings.size()) { + for (KeyValue<String, AnimationData> &E : animation_map) { + if (E.value.settings.size()) { Dictionary d; - for (Map<StringName, Variant>::Element *F = E->get().settings.front(); F; F = F->next()) { - d[String(F->key())] = F->get(); + for (const KeyValue<StringName, Variant> &F : E.value.settings) { + d[String(F.key)] = F.value; } - animations[E->key()] = d; + animations[E.key] = d; } } if (animations.size()) { @@ -889,8 +890,8 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { switch (current_action) { case ACTION_EXTRACT_MATERIALS: { - for (Map<String, MaterialData>::Element *E = material_map.front(); E; E = E->next()) { - MaterialData &md = material_map[E->key()]; + for (const KeyValue<String, MaterialData> &E : material_map) { + MaterialData &md = material_map[E.key]; TreeItem *item = external_path_tree->create_item(root); @@ -905,7 +906,7 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { item->set_text(2, "Already External"); item->set_tooltip(2, TTR("This material already references an external file, no action will be taken.\nDisable the external property for it to be extracted again.")); } else { - item->set_metadata(0, E->key()); + item->set_metadata(0, E.key); item->set_editable(0, true); item->set_checked(0, true); String path = p_path.plus_file(name); @@ -942,8 +943,8 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { external_paths->get_ok_button()->set_text(TTR("Extract")); } break; case ACTION_CHOOSE_MESH_SAVE_PATHS: { - for (Map<String, MeshData>::Element *E = mesh_map.front(); E; E = E->next()) { - MeshData &md = mesh_map[E->key()]; + for (const KeyValue<String, MeshData> &E : mesh_map) { + MeshData &md = mesh_map[E.key]; TreeItem *item = external_path_tree->create_item(root); @@ -958,7 +959,7 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { item->set_text(2, "Already Saving"); item->set_tooltip(2, TTR("This mesh already saves to an external resource, no action will be taken.")); } else { - item->set_metadata(0, E->key()); + item->set_metadata(0, E.key); item->set_editable(0, true); item->set_checked(0, true); String path = p_path.plus_file(name); @@ -995,8 +996,8 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { external_paths->get_ok_button()->set_text(TTR("Set Paths")); } break; case ACTION_CHOOSE_ANIMATION_SAVE_PATHS: { - for (Map<String, AnimationData>::Element *E = animation_map.front(); E; E = E->next()) { - AnimationData &ad = animation_map[E->key()]; + for (const KeyValue<String, AnimationData> &E : animation_map) { + AnimationData &ad = animation_map[E.key]; TreeItem *item = external_path_tree->create_item(root); @@ -1010,7 +1011,7 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { item->set_text(2, "Already Saving"); item->set_tooltip(2, TTR("This animation already saves to an external resource, no action will be taken.")); } else { - item->set_metadata(0, E->key()); + item->set_metadata(0, E.key); item->set_editable(0, true); item->set_checked(0, true); String path = p_path.plus_file(name); diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp deleted file mode 100644 index 5e6dd08e79..0000000000 --- a/editor/import/scene_importer_mesh.cpp +++ /dev/null @@ -1,889 +0,0 @@ -/*************************************************************************/ -/* scene_importer_mesh.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 "scene_importer_mesh.h" - -#include "core/math/math_defs.h" -#include "scene/resources/surface_tool.h" - -#include <cstdint> - -void EditorSceneImporterMesh::add_blend_shape(const String &p_name) { - ERR_FAIL_COND(surfaces.size() > 0); - blend_shapes.push_back(p_name); -} - -int EditorSceneImporterMesh::get_blend_shape_count() const { - return blend_shapes.size(); -} - -String EditorSceneImporterMesh::get_blend_shape_name(int p_blend_shape) const { - ERR_FAIL_INDEX_V(p_blend_shape, blend_shapes.size(), String()); - return blend_shapes[p_blend_shape]; -} - -void EditorSceneImporterMesh::set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode) { - blend_shape_mode = p_blend_shape_mode; -} - -Mesh::BlendShapeMode EditorSceneImporterMesh::get_blend_shape_mode() const { - return blend_shape_mode; -} - -void EditorSceneImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, const Ref<Material> &p_material, const String &p_name, const uint32_t p_flags) { - ERR_FAIL_COND(p_blend_shapes.size() != blend_shapes.size()); - ERR_FAIL_COND(p_arrays.size() != Mesh::ARRAY_MAX); - Surface s; - s.primitive = p_primitive; - s.arrays = p_arrays; - s.name = p_name; - s.flags = p_flags; - - Vector<Vector3> vertex_array = p_arrays[Mesh::ARRAY_VERTEX]; - int vertex_count = vertex_array.size(); - ERR_FAIL_COND(vertex_count == 0); - - for (int i = 0; i < blend_shapes.size(); i++) { - Array bsdata = p_blend_shapes[i]; - ERR_FAIL_COND(bsdata.size() != Mesh::ARRAY_MAX); - Vector<Vector3> vertex_data = bsdata[Mesh::ARRAY_VERTEX]; - ERR_FAIL_COND(vertex_data.size() != vertex_count); - Surface::BlendShape bs; - bs.arrays = bsdata; - s.blend_shape_data.push_back(bs); - } - - List<Variant> lods; - p_lods.get_key_list(&lods); - for (const Variant &E : lods) { - ERR_CONTINUE(!E.is_num()); - Surface::LOD lod; - lod.distance = E; - lod.indices = p_lods[E]; - ERR_CONTINUE(lod.indices.size() == 0); - s.lods.push_back(lod); - } - - s.material = p_material; - - surfaces.push_back(s); - mesh.unref(); -} - -int EditorSceneImporterMesh::get_surface_count() const { - return surfaces.size(); -} - -Mesh::PrimitiveType EditorSceneImporterMesh::get_surface_primitive_type(int p_surface) { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Mesh::PRIMITIVE_MAX); - return surfaces[p_surface].primitive; -} -Array EditorSceneImporterMesh::get_surface_arrays(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); - return surfaces[p_surface].arrays; -} -String EditorSceneImporterMesh::get_surface_name(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), String()); - return surfaces[p_surface].name; -} -void EditorSceneImporterMesh::set_surface_name(int p_surface, const String &p_name) { - ERR_FAIL_INDEX(p_surface, surfaces.size()); - surfaces.write[p_surface].name = p_name; - mesh.unref(); -} - -Array EditorSceneImporterMesh::get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); - ERR_FAIL_INDEX_V(p_blend_shape, surfaces[p_surface].blend_shape_data.size(), Array()); - return surfaces[p_surface].blend_shape_data[p_blend_shape].arrays; -} -int EditorSceneImporterMesh::get_surface_lod_count(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); - return surfaces[p_surface].lods.size(); -} -Vector<int> EditorSceneImporterMesh::get_surface_lod_indices(int p_surface, int p_lod) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Vector<int>()); - ERR_FAIL_INDEX_V(p_lod, surfaces[p_surface].lods.size(), Vector<int>()); - - return surfaces[p_surface].lods[p_lod].indices; -} - -float EditorSceneImporterMesh::get_surface_lod_size(int p_surface, int p_lod) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); - ERR_FAIL_INDEX_V(p_lod, surfaces[p_surface].lods.size(), 0); - return surfaces[p_surface].lods[p_lod].distance; -} - -uint32_t EditorSceneImporterMesh::get_surface_format(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); - return surfaces[p_surface].flags; -} - -Ref<Material> EditorSceneImporterMesh::get_surface_material(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Ref<Material>()); - return surfaces[p_surface].material; -} - -void EditorSceneImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_material) { - ERR_FAIL_INDEX(p_surface, surfaces.size()); - surfaces.write[p_surface].material = p_material; - mesh.unref(); -} - -Basis EditorSceneImporterMesh::compute_rotation_matrix_from_ortho_6d(Vector3 p_x_raw, Vector3 p_y_raw) { - Vector3 x = p_x_raw.normalized(); - Vector3 z = x.cross(p_y_raw); - z = z.normalized(); - Vector3 y = z.cross(x); - Basis basis; - basis.set_axis(Vector3::AXIS_X, x); - basis.set_axis(Vector3::AXIS_Y, y); - basis.set_axis(Vector3::AXIS_Z, z); - return basis; -} - -void EditorSceneImporterMesh::generate_lods() { - if (!SurfaceTool::simplify_func) { - return; - } - if (!SurfaceTool::simplify_scale_func) { - return; - } - if (!SurfaceTool::simplify_sloppy_func) { - return; - } - if (!SurfaceTool::simplify_with_attrib_func) { - return; - } - - for (int i = 0; i < surfaces.size(); i++) { - if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) { - continue; - } - - surfaces.write[i].lods.clear(); - Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; - Vector<int> indices = surfaces[i].arrays[RS::ARRAY_INDEX]; - if (indices.size() == 0) { - continue; //no lods if no indices - } - Vector<Vector3> normals = surfaces[i].arrays[RS::ARRAY_NORMAL]; - uint32_t vertex_count = vertices.size(); - const Vector3 *vertices_ptr = vertices.ptr(); - Vector<float> attributes; - Vector<float> normal_weights; - int32_t attribute_count = 6; - if (normals.size()) { - attributes.resize(normals.size() * attribute_count); - for (int32_t normal_i = 0; normal_i < normals.size(); normal_i++) { - Basis basis; - basis.set_euler(normals[normal_i]); - Vector3 basis_x = basis.get_axis(0); - Vector3 basis_y = basis.get_axis(1); - basis = compute_rotation_matrix_from_ortho_6d(basis_x, basis_y); - basis_x = basis.get_axis(0); - basis_y = basis.get_axis(1); - attributes.write[normal_i * attribute_count + 0] = basis_x.x; - attributes.write[normal_i * attribute_count + 1] = basis_x.y; - attributes.write[normal_i * attribute_count + 2] = basis_x.z; - attributes.write[normal_i * attribute_count + 3] = basis_y.x; - attributes.write[normal_i * attribute_count + 4] = basis_y.y; - attributes.write[normal_i * attribute_count + 5] = basis_y.z; - } - normal_weights.resize(vertex_count); - for (int32_t weight_i = 0; weight_i < normal_weights.size(); weight_i++) { - normal_weights.write[weight_i] = 1.0; - } - } else { - attribute_count = 0; - } - const int min_indices = 10; - const float error_tolerance = 1.44224'95703; // Cube root of 3 - const float threshold = 1.0 / error_tolerance; - int index_target = indices.size() * threshold; - float max_mesh_error_percentage = 1e0f; - float mesh_error = 0.0f; - float scale = SurfaceTool::simplify_scale_func((const float *)vertices_ptr, vertex_count, sizeof(Vector3)); - while (index_target > min_indices) { - Vector<int> new_indices; - new_indices.resize(indices.size()); - size_t new_len = SurfaceTool::simplify_with_attrib_func((unsigned int *)new_indices.ptrw(), (const unsigned int *)indices.ptr(), indices.size(), (const float *)vertices_ptr, vertex_count, sizeof(Vector3), index_target, max_mesh_error_percentage, &mesh_error, (float *)attributes.ptrw(), normal_weights.ptrw(), attribute_count); - if ((int)new_len > (index_target * error_tolerance)) { - break; - } - Surface::LOD lod; - lod.distance = mesh_error * scale; - if (Math::is_zero_approx(mesh_error)) { - break; - } - if (new_len <= 0) { - break; - } - new_indices.resize(new_len); - lod.indices = new_indices; - print_line("Lod " + itos(surfaces.write[i].lods.size()) + " begin with " + itos(indices.size() / 3) + " triangles and shoot for " + itos(index_target / 3) + " triangles. Got " + itos(new_len / 3) + " triangles. Lod screen ratio " + rtos(lod.distance)); - surfaces.write[i].lods.push_back(lod); - index_target *= threshold; - } - } -} - -bool EditorSceneImporterMesh::has_mesh() const { - return mesh.is_valid(); -} - -Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh(const Ref<ArrayMesh> &p_base) { - ERR_FAIL_COND_V(surfaces.size() == 0, Ref<ArrayMesh>()); - - if (mesh.is_null()) { - if (p_base.is_valid()) { - mesh = p_base; - } - if (mesh.is_null()) { - mesh.instantiate(); - } - mesh->set_name(get_name()); - if (has_meta("import_id")) { - mesh->set_meta("import_id", get_meta("import_id")); - } - for (int i = 0; i < blend_shapes.size(); i++) { - mesh->add_blend_shape(blend_shapes[i]); - } - mesh->set_blend_shape_mode(blend_shape_mode); - for (int i = 0; i < surfaces.size(); i++) { - Array bs_data; - if (surfaces[i].blend_shape_data.size()) { - for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) { - bs_data.push_back(surfaces[i].blend_shape_data[j].arrays); - } - } - Dictionary lods; - if (surfaces[i].lods.size()) { - for (int j = 0; j < surfaces[i].lods.size(); j++) { - lods[surfaces[i].lods[j].distance] = surfaces[i].lods[j].indices; - } - } - - mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods, surfaces[i].flags); - if (surfaces[i].material.is_valid()) { - mesh->surface_set_material(mesh->get_surface_count() - 1, surfaces[i].material); - } - if (surfaces[i].name != String()) { - mesh->surface_set_name(mesh->get_surface_count() - 1, surfaces[i].name); - } - } - - mesh->set_lightmap_size_hint(lightmap_size_hint); - - if (shadow_mesh.is_valid()) { - Ref<ArrayMesh> shadow = shadow_mesh->get_mesh(); - mesh->set_shadow_mesh(shadow); - } - } - - return mesh; -} - -void EditorSceneImporterMesh::clear() { - surfaces.clear(); - blend_shapes.clear(); - mesh.unref(); -} - -void EditorSceneImporterMesh::create_shadow_mesh() { - if (shadow_mesh.is_valid()) { - shadow_mesh.unref(); - } - - //no shadow mesh for blendshapes - if (blend_shapes.size() > 0) { - return; - } - //no shadow mesh for skeletons - for (int i = 0; i < surfaces.size(); i++) { - if (surfaces[i].arrays[RS::ARRAY_BONES].get_type() != Variant::NIL) { - return; - } - if (surfaces[i].arrays[RS::ARRAY_WEIGHTS].get_type() != Variant::NIL) { - return; - } - } - - shadow_mesh.instantiate(); - - for (int i = 0; i < surfaces.size(); i++) { - LocalVector<int> vertex_remap; - Vector<Vector3> new_vertices; - Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; - int vertex_count = vertices.size(); - { - Map<Vector3, int> unique_vertices; - const Vector3 *vptr = vertices.ptr(); - for (int j = 0; j < vertex_count; j++) { - Vector3 v = vptr[j]; - - Map<Vector3, int>::Element *E = unique_vertices.find(v); - - if (E) { - vertex_remap.push_back(E->get()); - } else { - int vcount = unique_vertices.size(); - unique_vertices[v] = vcount; - vertex_remap.push_back(vcount); - new_vertices.push_back(v); - } - } - } - - Array new_surface; - new_surface.resize(RS::ARRAY_MAX); - Dictionary lods; - - // print_line("original vertex count: " + itos(vertices.size()) + " new vertex count: " + itos(new_vertices.size())); - - new_surface[RS::ARRAY_VERTEX] = new_vertices; - - Vector<int> indices = surfaces[i].arrays[RS::ARRAY_INDEX]; - if (indices.size()) { - int index_count = indices.size(); - const int *index_rptr = indices.ptr(); - Vector<int> new_indices; - new_indices.resize(indices.size()); - int *index_wptr = new_indices.ptrw(); - - for (int j = 0; j < index_count; j++) { - int index = index_rptr[j]; - ERR_FAIL_INDEX(index, vertex_count); - index_wptr[j] = vertex_remap[index]; - } - - new_surface[RS::ARRAY_INDEX] = new_indices; - - // Make sure the same LODs as the full version are used. - // This makes it more coherent between rendered model and its shadows. - for (int j = 0; j < surfaces[i].lods.size(); j++) { - indices = surfaces[i].lods[j].indices; - - index_count = indices.size(); - index_rptr = indices.ptr(); - new_indices.resize(indices.size()); - index_wptr = new_indices.ptrw(); - - for (int k = 0; k < index_count; k++) { - int index = index_rptr[j]; - ERR_FAIL_INDEX(index, vertex_count); - index_wptr[j] = vertex_remap[index]; - } - - lods[surfaces[i].lods[j].distance] = new_indices; - } - } - - shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref<Material>(), surfaces[i].name, surfaces[i].flags); - } -} - -Ref<EditorSceneImporterMesh> EditorSceneImporterMesh::get_shadow_mesh() const { - return shadow_mesh; -} - -void EditorSceneImporterMesh::_set_data(const Dictionary &p_data) { - clear(); - if (p_data.has("blend_shape_names")) { - blend_shapes = p_data["blend_shape_names"]; - } - if (p_data.has("surfaces")) { - Array surface_arr = p_data["surfaces"]; - for (int i = 0; i < surface_arr.size(); i++) { - Dictionary s = surface_arr[i]; - ERR_CONTINUE(!s.has("primitive")); - ERR_CONTINUE(!s.has("arrays")); - Mesh::PrimitiveType prim = Mesh::PrimitiveType(int(s["primitive"])); - ERR_CONTINUE(prim >= Mesh::PRIMITIVE_MAX); - Array arr = s["arrays"]; - Dictionary lods; - String name; - if (s.has("name")) { - name = s["name"]; - } - if (s.has("lods")) { - lods = s["lods"]; - } - Array blend_shapes; - if (s.has("blend_shapes")) { - blend_shapes = s["blend_shapes"]; - } - Ref<Material> material; - if (s.has("material")) { - material = s["material"]; - } - uint32_t flags = 0; - if (s.has("flags")) { - flags = s["flags"]; - } - add_surface(prim, arr, blend_shapes, lods, material, name, flags); - } - } -} -Dictionary EditorSceneImporterMesh::_get_data() const { - Dictionary data; - if (blend_shapes.size()) { - data["blend_shape_names"] = blend_shapes; - } - Array surface_arr; - for (int i = 0; i < surfaces.size(); i++) { - Dictionary d; - d["primitive"] = surfaces[i].primitive; - d["arrays"] = surfaces[i].arrays; - if (surfaces[i].blend_shape_data.size()) { - Array bs_data; - for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) { - bs_data.push_back(surfaces[i].blend_shape_data[j].arrays); - } - d["blend_shapes"] = bs_data; - } - if (surfaces[i].lods.size()) { - Dictionary lods; - for (int j = 0; j < surfaces[i].lods.size(); j++) { - lods[surfaces[i].lods[j].distance] = surfaces[i].lods[j].indices; - } - d["lods"] = lods; - } - - if (surfaces[i].material.is_valid()) { - d["material"] = surfaces[i].material; - } - - if (surfaces[i].name != String()) { - d["name"] = surfaces[i].name; - } - - if (surfaces[i].flags != 0) { - d["flags"] = surfaces[i].flags; - } - - surface_arr.push_back(d); - } - data["surfaces"] = surface_arr; - return data; -} - -Vector<Face3> EditorSceneImporterMesh::get_faces() const { - Vector<Face3> faces; - for (int i = 0; i < surfaces.size(); i++) { - if (surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) { - Vector<Vector3> vertices = surfaces[i].arrays[Mesh::ARRAY_VERTEX]; - Vector<int> indices = surfaces[i].arrays[Mesh::ARRAY_INDEX]; - if (indices.size()) { - for (int j = 0; j < indices.size(); j += 3) { - Face3 f; - f.vertex[0] = vertices[indices[j + 0]]; - f.vertex[1] = vertices[indices[j + 1]]; - f.vertex[2] = vertices[indices[j + 2]]; - faces.push_back(f); - } - } else { - for (int j = 0; j < vertices.size(); j += 3) { - Face3 f; - f.vertex[0] = vertices[j + 0]; - f.vertex[1] = vertices[j + 1]; - f.vertex[2] = vertices[j + 2]; - faces.push_back(f); - } - } - } - } - - return faces; -} - -Vector<Ref<Shape3D>> EditorSceneImporterMesh::convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const { - ERR_FAIL_COND_V(!Mesh::convex_decomposition_function, Vector<Ref<Shape3D>>()); - - const Vector<Face3> faces = get_faces(); - int face_count = faces.size(); - - Vector<Vector3> vertices; - uint32_t vertex_count = 0; - vertices.resize(face_count * 3); - Vector<uint32_t> indices; - indices.resize(face_count * 3); - { - Map<Vector3, uint32_t> vertex_map; - Vector3 *vertex_w = vertices.ptrw(); - uint32_t *index_w = indices.ptrw(); - for (int i = 0; i < face_count; i++) { - for (int j = 0; j < 3; j++) { - const Vector3 &vertex = faces[i].vertex[j]; - Map<Vector3, uint32_t>::Element *found_vertex = vertex_map.find(vertex); - uint32_t index; - if (found_vertex) { - index = found_vertex->get(); - } else { - index = ++vertex_count; - vertex_map[vertex] = index; - vertex_w[index] = vertex; - } - index_w[i * 3 + j] = index; - } - } - } - vertices.resize(vertex_count); - - Vector<Vector<Vector3>> decomposed = Mesh::convex_decomposition_function((real_t *)vertices.ptr(), vertex_count, indices.ptr(), face_count, p_settings, nullptr); - - Vector<Ref<Shape3D>> ret; - - for (int i = 0; i < decomposed.size(); i++) { - Ref<ConvexPolygonShape3D> shape; - shape.instantiate(); - shape->set_points(decomposed[i]); - ret.push_back(shape); - } - - return ret; -} - -Ref<Shape3D> EditorSceneImporterMesh::create_trimesh_shape() const { - Vector<Face3> faces = get_faces(); - if (faces.size() == 0) { - return Ref<Shape3D>(); - } - - Vector<Vector3> face_points; - face_points.resize(faces.size() * 3); - - for (int i = 0; i < face_points.size(); i += 3) { - Face3 f = faces.get(i / 3); - face_points.set(i, f.vertex[0]); - face_points.set(i + 1, f.vertex[1]); - face_points.set(i + 2, f.vertex[2]); - } - - Ref<ConcavePolygonShape3D> shape = memnew(ConcavePolygonShape3D); - shape->set_faces(face_points); - return shape; -} - -Ref<NavigationMesh> EditorSceneImporterMesh::create_navigation_mesh() { - Vector<Face3> faces = get_faces(); - if (faces.size() == 0) { - return Ref<NavigationMesh>(); - } - - Map<Vector3, int> unique_vertices; - LocalVector<int> face_indices; - - for (int i = 0; i < faces.size(); i++) { - for (int j = 0; j < 3; j++) { - Vector3 v = faces[i].vertex[j]; - int idx; - if (unique_vertices.has(v)) { - idx = unique_vertices[v]; - } else { - idx = unique_vertices.size(); - unique_vertices[v] = idx; - } - face_indices.push_back(idx); - } - } - - Vector<Vector3> vertices; - vertices.resize(unique_vertices.size()); - for (Map<Vector3, int>::Element *E = unique_vertices.front(); E; E = E->next()) { - vertices.write[E->get()] = E->key(); - } - - Ref<NavigationMesh> nm; - nm.instantiate(); - nm->set_vertices(vertices); - - Vector<int> v3; - v3.resize(3); - for (uint32_t i = 0; i < face_indices.size(); i += 3) { - v3.write[0] = face_indices[i + 0]; - v3.write[1] = face_indices[i + 1]; - v3.write[2] = face_indices[i + 2]; - nm->add_polygon(v3); - } - - return nm; -} - -extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y); - -struct EditorSceneImporterMeshLightmapSurface { - Ref<Material> material; - LocalVector<SurfaceTool::Vertex> vertices; - Mesh::PrimitiveType primitive = Mesh::PrimitiveType::PRIMITIVE_MAX; - uint32_t format = 0; - String name; -}; - -Error EditorSceneImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache) { - ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); - ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); - - LocalVector<float> vertices; - LocalVector<float> normals; - LocalVector<int> indices; - LocalVector<float> uv; - LocalVector<Pair<int, int>> uv_indices; - - Vector<EditorSceneImporterMeshLightmapSurface> lightmap_surfaces; - - // Keep only the scale - Basis basis = p_base_transform.get_basis(); - Vector3 scale = Vector3(basis.get_axis(0).length(), basis.get_axis(1).length(), basis.get_axis(2).length()); - - Transform3D transform; - transform.scale(scale); - - Basis normal_basis = transform.basis.inverse().transposed(); - - for (int i = 0; i < get_surface_count(); i++) { - EditorSceneImporterMeshLightmapSurface s; - s.primitive = get_surface_primitive_type(i); - - ERR_FAIL_COND_V_MSG(s.primitive != Mesh::PRIMITIVE_TRIANGLES, ERR_UNAVAILABLE, "Only triangles are supported for lightmap unwrap."); - Array arrays = get_surface_arrays(i); - s.material = get_surface_material(i); - s.name = get_surface_name(i); - - SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); - - PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX]; - int vc = rvertices.size(); - - PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL]; - - int vertex_ofs = vertices.size() / 3; - - vertices.resize((vertex_ofs + vc) * 3); - normals.resize((vertex_ofs + vc) * 3); - uv_indices.resize(vertex_ofs + vc); - - for (int j = 0; j < vc; j++) { - Vector3 v = transform.xform(rvertices[j]); - Vector3 n = normal_basis.xform(rnormals[j]).normalized(); - - vertices[(j + vertex_ofs) * 3 + 0] = v.x; - vertices[(j + vertex_ofs) * 3 + 1] = v.y; - vertices[(j + vertex_ofs) * 3 + 2] = v.z; - normals[(j + vertex_ofs) * 3 + 0] = n.x; - normals[(j + vertex_ofs) * 3 + 1] = n.y; - normals[(j + vertex_ofs) * 3 + 2] = n.z; - uv_indices[j + vertex_ofs] = Pair<int, int>(i, j); - } - - PackedInt32Array rindices = arrays[Mesh::ARRAY_INDEX]; - int ic = rindices.size(); - - float eps = 1.19209290e-7F; // Taken from xatlas.h - if (ic == 0) { - for (int j = 0; j < vc / 3; j++) { - Vector3 p0 = transform.xform(rvertices[j * 3 + 0]); - Vector3 p1 = transform.xform(rvertices[j * 3 + 1]); - Vector3 p2 = transform.xform(rvertices[j * 3 + 2]); - - if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { - continue; - } - - indices.push_back(vertex_ofs + j * 3 + 0); - indices.push_back(vertex_ofs + j * 3 + 1); - indices.push_back(vertex_ofs + j * 3 + 2); - } - - } else { - for (int j = 0; j < ic / 3; j++) { - Vector3 p0 = transform.xform(rvertices[rindices[j * 3 + 0]]); - Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]); - Vector3 p2 = transform.xform(rvertices[rindices[j * 3 + 2]]); - - if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { - continue; - } - - indices.push_back(vertex_ofs + rindices[j * 3 + 0]); - indices.push_back(vertex_ofs + rindices[j * 3 + 1]); - indices.push_back(vertex_ofs + rindices[j * 3 + 2]); - } - } - - lightmap_surfaces.push_back(s); - } - - //unwrap - - bool use_cache = true; // Used to request cache generation and to know if cache was used - uint8_t *gen_cache; - int gen_cache_size; - float *gen_uvs; - int *gen_vertices; - int *gen_indices; - int gen_vertex_count; - int gen_index_count; - int size_x; - int size_y; - - bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), p_src_cache.ptr(), &use_cache, &gen_cache, &gen_cache_size, &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); - - if (!ok) { - return ERR_CANT_CREATE; - } - - //remove surfaces - clear(); - - //create surfacetools for each surface.. - LocalVector<Ref<SurfaceTool>> surfaces_tools; - - for (int i = 0; i < lightmap_surfaces.size(); i++) { - Ref<SurfaceTool> st; - st.instantiate(); - st->begin(Mesh::PRIMITIVE_TRIANGLES); - st->set_material(lightmap_surfaces[i].material); - st->set_meta("name", lightmap_surfaces[i].name); - surfaces_tools.push_back(st); //stay there - } - - print_verbose("Mesh: Gen indices: " + itos(gen_index_count)); - - //go through all indices - for (int i = 0; i < gen_index_count; i += 3) { - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], (int)uv_indices.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], (int)uv_indices.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], (int)uv_indices.size(), ERR_BUG); - - ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); - - int surface = uv_indices[gen_vertices[gen_indices[i + 0]]].first; - - for (int j = 0; j < 3; j++) { - SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second]; - - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_COLOR) { - surfaces_tools[surface]->set_color(v.color); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TEX_UV) { - surfaces_tools[surface]->set_uv(v.uv); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_NORMAL) { - surfaces_tools[surface]->set_normal(v.normal); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TANGENT) { - Plane t; - t.normal = v.tangent; - t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; - surfaces_tools[surface]->set_tangent(t); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_BONES) { - surfaces_tools[surface]->set_bones(v.bones); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_WEIGHTS) { - surfaces_tools[surface]->set_weights(v.weights); - } - - Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); - surfaces_tools[surface]->set_uv2(uv2); - - surfaces_tools[surface]->add_vertex(v.vertex); - } - } - - //generate surfaces - for (unsigned int i = 0; i < surfaces_tools.size(); i++) { - surfaces_tools[i]->index(); - Array arrays = surfaces_tools[i]->commit_to_arrays(); - add_surface(surfaces_tools[i]->get_primitive(), arrays, Array(), Dictionary(), surfaces_tools[i]->get_material(), surfaces_tools[i]->get_meta("name")); - } - - set_lightmap_size_hint(Size2(size_x, size_y)); - - if (gen_cache_size > 0) { - r_dst_cache.resize(gen_cache_size); - memcpy(r_dst_cache.ptrw(), gen_cache, gen_cache_size); - memfree(gen_cache); - } - - if (!use_cache) { - // Cache was not used, free the buffers - memfree(gen_vertices); - memfree(gen_indices); - memfree(gen_uvs); - } - - return OK; -} - -void EditorSceneImporterMesh::set_lightmap_size_hint(const Size2i &p_size) { - lightmap_size_hint = p_size; -} - -Size2i EditorSceneImporterMesh::get_lightmap_size_hint() const { - return lightmap_size_hint; -} - -void EditorSceneImporterMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_blend_shape", "name"), &EditorSceneImporterMesh::add_blend_shape); - ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &EditorSceneImporterMesh::get_blend_shape_count); - ClassDB::bind_method(D_METHOD("get_blend_shape_name", "blend_shape_idx"), &EditorSceneImporterMesh::get_blend_shape_name); - - ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &EditorSceneImporterMesh::set_blend_shape_mode); - ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &EditorSceneImporterMesh::get_blend_shape_mode); - - ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name", "flags"), &EditorSceneImporterMesh::add_surface, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(Ref<Material>()), DEFVAL(String()), DEFVAL(0)); - - ClassDB::bind_method(D_METHOD("get_surface_count"), &EditorSceneImporterMesh::get_surface_count); - ClassDB::bind_method(D_METHOD("get_surface_primitive_type", "surface_idx"), &EditorSceneImporterMesh::get_surface_primitive_type); - ClassDB::bind_method(D_METHOD("get_surface_name", "surface_idx"), &EditorSceneImporterMesh::get_surface_name); - ClassDB::bind_method(D_METHOD("get_surface_arrays", "surface_idx"), &EditorSceneImporterMesh::get_surface_arrays); - ClassDB::bind_method(D_METHOD("get_surface_blend_shape_arrays", "surface_idx", "blend_shape_idx"), &EditorSceneImporterMesh::get_surface_blend_shape_arrays); - ClassDB::bind_method(D_METHOD("get_surface_lod_count", "surface_idx"), &EditorSceneImporterMesh::get_surface_lod_count); - ClassDB::bind_method(D_METHOD("get_surface_lod_size", "surface_idx", "lod_idx"), &EditorSceneImporterMesh::get_surface_lod_size); - ClassDB::bind_method(D_METHOD("get_surface_lod_indices", "surface_idx", "lod_idx"), &EditorSceneImporterMesh::get_surface_lod_indices); - ClassDB::bind_method(D_METHOD("get_surface_material", "surface_idx"), &EditorSceneImporterMesh::get_surface_material); - ClassDB::bind_method(D_METHOD("get_surface_format", "surface_idx"), &EditorSceneImporterMesh::get_surface_format); - - ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &EditorSceneImporterMesh::set_surface_name); - ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &EditorSceneImporterMesh::set_surface_material); - - ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &EditorSceneImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>())); - ClassDB::bind_method(D_METHOD("clear"), &EditorSceneImporterMesh::clear); - - ClassDB::bind_method(D_METHOD("_set_data", "data"), &EditorSceneImporterMesh::_set_data); - ClassDB::bind_method(D_METHOD("_get_data"), &EditorSceneImporterMesh::_get_data); - - ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &EditorSceneImporterMesh::set_lightmap_size_hint); - ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &EditorSceneImporterMesh::get_lightmap_size_hint); - - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_data", "_get_data"); -} diff --git a/editor/import/scene_importer_mesh.h b/editor/import/scene_importer_mesh.h deleted file mode 100644 index d32b1fdf74..0000000000 --- a/editor/import/scene_importer_mesh.h +++ /dev/null @@ -1,124 +0,0 @@ -/*************************************************************************/ -/* scene_importer_mesh.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 EDITOR_SCENE_IMPORTER_MESH_H -#define EDITOR_SCENE_IMPORTER_MESH_H - -#include "core/io/resource.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/mesh.h" -#include "scene/resources/navigation_mesh.h" - -#include <cstdint> - -// The following classes are used by importers instead of ArrayMesh and MeshInstance3D -// so the data is not registered (hence, quality loss), importing happens faster and -// its easier to modify before saving - -class EditorSceneImporterMesh : public Resource { - GDCLASS(EditorSceneImporterMesh, Resource) - - struct Surface { - Mesh::PrimitiveType primitive; - Array arrays; - struct BlendShape { - Array arrays; - }; - Vector<BlendShape> blend_shape_data; - struct LOD { - Vector<int> indices; - float distance; - }; - Vector<LOD> lods; - Ref<Material> material; - String name; - uint32_t flags = 0; - }; - Vector<Surface> surfaces; - Vector<String> blend_shapes; - Mesh::BlendShapeMode blend_shape_mode = Mesh::BLEND_SHAPE_MODE_NORMALIZED; - - Ref<ArrayMesh> mesh; - - Ref<EditorSceneImporterMesh> shadow_mesh; - - Size2i lightmap_size_hint; - Basis compute_rotation_matrix_from_ortho_6d(Vector3 p_x_raw, Vector3 y_raw); - -protected: - void _set_data(const Dictionary &p_data); - Dictionary _get_data() const; - - static void _bind_methods(); - -public: - void add_blend_shape(const String &p_name); - int get_blend_shape_count() const; - String get_blend_shape_name(int p_blend_shape) const; - - void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String(), const uint32_t p_flags = 0); - int get_surface_count() const; - - void set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode); - Mesh::BlendShapeMode get_blend_shape_mode() const; - - Mesh::PrimitiveType get_surface_primitive_type(int p_surface); - String get_surface_name(int p_surface) const; - void set_surface_name(int p_surface, const String &p_name); - Array get_surface_arrays(int p_surface) const; - Array get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const; - int get_surface_lod_count(int p_surface) const; - Vector<int> get_surface_lod_indices(int p_surface, int p_lod) const; - float get_surface_lod_size(int p_surface, int p_lod) const; - Ref<Material> get_surface_material(int p_surface) const; - uint32_t get_surface_format(int p_surface) const; - - void set_surface_material(int p_surface, const Ref<Material> &p_material); - - void generate_lods(); - - void create_shadow_mesh(); - Ref<EditorSceneImporterMesh> get_shadow_mesh() const; - - Vector<Face3> get_faces() const; - Vector<Ref<Shape3D>> convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const; - Ref<Shape3D> create_trimesh_shape() const; - Ref<NavigationMesh> create_navigation_mesh(); - Error lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache); - - void set_lightmap_size_hint(const Size2i &p_size); - Size2i get_lightmap_size_hint() const; - - bool has_mesh() const; - Ref<ArrayMesh> get_mesh(const Ref<ArrayMesh> &p_base = Ref<ArrayMesh>()); - void clear(); -}; -#endif // EDITOR_SCENE_IMPORTER_MESH_H diff --git a/editor/import/scene_importer_mesh_node_3d.cpp b/editor/import/scene_importer_mesh_node_3d.cpp deleted file mode 100644 index 3c201cf674..0000000000 --- a/editor/import/scene_importer_mesh_node_3d.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/*************************************************************************/ -/* scene_importer_mesh_node_3d.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 "scene_importer_mesh_node_3d.h" - -void EditorSceneImporterMeshNode3D::set_mesh(const Ref<EditorSceneImporterMesh> &p_mesh) { - mesh = p_mesh; -} -Ref<EditorSceneImporterMesh> EditorSceneImporterMeshNode3D::get_mesh() const { - return mesh; -} - -void EditorSceneImporterMeshNode3D::set_skin(const Ref<Skin> &p_skin) { - skin = p_skin; -} -Ref<Skin> EditorSceneImporterMeshNode3D::get_skin() const { - return skin; -} - -void EditorSceneImporterMeshNode3D::set_surface_material(int p_idx, const Ref<Material> &p_material) { - ERR_FAIL_COND(p_idx < 0); - if (p_idx >= surface_materials.size()) { - surface_materials.resize(p_idx + 1); - } - - surface_materials.write[p_idx] = p_material; -} -Ref<Material> EditorSceneImporterMeshNode3D::get_surface_material(int p_idx) const { - ERR_FAIL_COND_V(p_idx < 0, Ref<Material>()); - if (p_idx >= surface_materials.size()) { - return Ref<Material>(); - } - return surface_materials[p_idx]; -} - -void EditorSceneImporterMeshNode3D::set_skeleton_path(const NodePath &p_path) { - skeleton_path = p_path; -} -NodePath EditorSceneImporterMeshNode3D::get_skeleton_path() const { - return skeleton_path; -} - -void EditorSceneImporterMeshNode3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &EditorSceneImporterMeshNode3D::set_mesh); - ClassDB::bind_method(D_METHOD("get_mesh"), &EditorSceneImporterMeshNode3D::get_mesh); - - ClassDB::bind_method(D_METHOD("set_skin", "skin"), &EditorSceneImporterMeshNode3D::set_skin); - ClassDB::bind_method(D_METHOD("get_skin"), &EditorSceneImporterMeshNode3D::get_skin); - - ClassDB::bind_method(D_METHOD("set_skeleton_path", "skeleton_path"), &EditorSceneImporterMeshNode3D::set_skeleton_path); - ClassDB::bind_method(D_METHOD("get_skeleton_path"), &EditorSceneImporterMeshNode3D::get_skeleton_path); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "EditorSceneImporterMesh"), "set_mesh", "get_mesh"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton"), "set_skeleton_path", "get_skeleton_path"); -} diff --git a/editor/import/scene_importer_mesh_node_3d.h b/editor/import/scene_importer_mesh_node_3d.h deleted file mode 100644 index dec1717c99..0000000000 --- a/editor/import/scene_importer_mesh_node_3d.h +++ /dev/null @@ -1,64 +0,0 @@ -/*************************************************************************/ -/* scene_importer_mesh_node_3d.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 EDITOR_SCENE_IMPORTER_MESH_NODE_3D_H -#define EDITOR_SCENE_IMPORTER_MESH_NODE_3D_H - -#include "editor/import/scene_importer_mesh.h" -#include "scene/3d/node_3d.h" -#include "scene/resources/skin.h" - -class EditorSceneImporterMesh; - -class EditorSceneImporterMeshNode3D : public Node3D { - GDCLASS(EditorSceneImporterMeshNode3D, Node3D) - - Ref<EditorSceneImporterMesh> mesh; - Ref<Skin> skin; - NodePath skeleton_path; - Vector<Ref<Material>> surface_materials; - -protected: - static void _bind_methods(); - -public: - void set_mesh(const Ref<EditorSceneImporterMesh> &p_mesh); - Ref<EditorSceneImporterMesh> get_mesh() const; - - void set_skin(const Ref<Skin> &p_skin); - Ref<Skin> get_skin() const; - - void set_surface_material(int p_idx, const Ref<Material> &p_material); - Ref<Material> get_surface_material(int p_idx) const; - - void set_skeleton_path(const NodePath &p_path); - NodePath get_skeleton_path() const; -}; -#endif diff --git a/editor/import_defaults_editor.cpp b/editor/import_defaults_editor.cpp index 25bca1d4f4..8300dcf555 100644 --- a/editor/import_defaults_editor.cpp +++ b/editor/import_defaults_editor.cpp @@ -86,9 +86,9 @@ void ImportDefaultsEditor::_save() { if (settings->importer.is_valid()) { Dictionary modified; - for (Map<StringName, Variant>::Element *E = settings->values.front(); E; E = E->next()) { - if (E->get() != settings->default_values[E->key()]) { - modified[E->key()] = E->get(); + for (const KeyValue<StringName, Variant> &E : settings->values) { + if (E.value != settings->default_values[E.key]) { + modified[E.key] = E.value; } } 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/inspector_dock.cpp b/editor/inspector_dock.cpp index 04ddf3552b..59d0b92ba0 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -204,11 +204,24 @@ void InspectorDock::_load_resource(const String &p_type) { load_resource_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++) { + load_resource_dialog->add_filter("*." + textfile_ext[i] + " ; " + textfile_ext[i].to_upper()); + } + load_resource_dialog->popup_file_dialog(); } void InspectorDock::_resource_file_selected(String p_file) { - RES res = ResourceLoader::load(p_file); + RES res; + if (ResourceLoader::exists(p_file, "")) { + res = ResourceLoader::load(p_file); + } else { + const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + if (textfile_ext.has(p_file.get_extension())) { + res = ScriptEditor::get_singleton()->open_file(p_file); + } + } if (res.is_null()) { warning_dialog->set_text(TTR("Failed to load resource.")); @@ -370,7 +383,7 @@ void InspectorDock::_menu_expandall() { } void InspectorDock::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance); } void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Transform3D &p_key) { @@ -378,7 +391,7 @@ void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Tran if (!s) { return; } - AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(s, p_sub, p_key); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(s, p_sub, p_key); } void InspectorDock::_warning_pressed() { @@ -469,18 +482,19 @@ void InspectorDock::update(Object *p_object) { const bool is_object = p_object != nullptr; const bool is_resource = is_object && p_object->is_class("Resource"); + const bool is_text_file = is_object && p_object->is_class("TextFile"); const bool is_node = is_object && p_object->is_class("Node"); - object_menu->set_disabled(!is_object); - search->set_editable(is_object); - resource_save_button->set_disabled(!is_resource); - open_docs_button->set_disabled(!is_resource && !is_node); + object_menu->set_disabled(!is_object || is_text_file); + search->set_editable(is_object && !is_text_file); + resource_save_button->set_disabled(!is_resource || is_text_file); + open_docs_button->set_disabled(is_text_file || (!is_resource && !is_node)); PopupMenu *resource_extra_popup = resource_extra_button->get_popup(); - resource_extra_popup->set_item_disabled(resource_extra_popup->get_item_index(RESOURCE_COPY), !is_resource); - resource_extra_popup->set_item_disabled(resource_extra_popup->get_item_index(RESOURCE_MAKE_BUILT_IN), !is_resource); + resource_extra_popup->set_item_disabled(resource_extra_popup->get_item_index(RESOURCE_COPY), !is_resource || is_text_file); + resource_extra_popup->set_item_disabled(resource_extra_popup->get_item_index(RESOURCE_MAKE_BUILT_IN), !is_resource || is_text_file); - if (!is_object) { + if (!is_object || is_text_file) { warning->hide(); editor_path->clear_path(); return; @@ -531,7 +545,7 @@ void InspectorDock::go_back() { void InspectorDock::update_keying() { bool valid = false; - if (AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) { + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) { EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history(); if (editor_history->get_path_size() >= 1) { Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0)); diff --git a/editor/plugin_config_dialog.cpp b/editor/plugin_config_dialog.cpp index a3ff312497..91ca1465df 100644 --- a/editor/plugin_config_dialog.cpp +++ b/editor/plugin_config_dialog.cpp @@ -124,6 +124,10 @@ void PluginConfigDialog::_on_cancelled() { _clear_fields(); } +void PluginConfigDialog::_on_language_changed(const int) { + _on_required_text_changed(String()); +} + void PluginConfigDialog::_on_required_text_changed(const String &) { int lang_idx = script_option_edit->get_selected(); String ext = ScriptServer::get_language(lang_idx)->get_extension(); @@ -161,6 +165,9 @@ void PluginConfigDialog::_on_required_text_changed(const String &) { is_valid = false; subfolder_validation->set_texture(invalid_icon); subfolder_validation->set_tooltip(TTR("Subfolder cannot be blank.")); + } else if (!subfolder_edit->get_text().is_valid_filename()) { + subfolder_validation->set_texture(invalid_icon); + subfolder_validation->set_tooltip(TTR("Subfolder name is not a valid folder name.")); } else { DirAccessRef dir = DirAccess::create(DirAccess::ACCESS_RESOURCES); String path = "res://addons/" + subfolder_edit->get_text(); @@ -330,6 +337,7 @@ PluginConfigDialog::PluginConfigDialog() { } script_option_edit->select(default_lang); grid->add_child(script_option_edit); + script_option_edit->connect("item_selected", callable_mp(this, &PluginConfigDialog::_on_language_changed)); // Plugin Script Name Label *script_lb = memnew(Label); diff --git a/editor/plugin_config_dialog.h b/editor/plugin_config_dialog.h index ad5b96735f..45fcdb6b6e 100644 --- a/editor/plugin_config_dialog.h +++ b/editor/plugin_config_dialog.h @@ -59,6 +59,7 @@ class PluginConfigDialog : public ConfirmationDialog { void _clear_fields(); void _on_confirmed(); void _on_cancelled(); + void _on_language_changed(const int p_language); void _on_required_text_changed(const String &p_text); static String _to_absolute_plugin_path(const String &p_plugin_name); diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 49fcac512b..686a35e442 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -129,8 +129,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven add_point_pos += blend_space->get_min_space(); if (snap->is_pressed()) { - add_point_pos.x = Math::snapped(add_point_pos.x, blend_space->get_snap().x); - add_point_pos.y = Math::snapped(add_point_pos.y, blend_space->get_snap().y); + add_point_pos = add_point_pos.snapped(blend_space->get_snap()); } } @@ -215,8 +214,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven Vector2 point = blend_space->get_blend_point_position(selected_point); point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } updating = true; @@ -467,8 +465,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { if (dragging_selected && selected_point == point_idx) { point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); @@ -503,8 +500,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { if (dragging_selected && selected_point == i) { point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); @@ -702,8 +698,7 @@ void AnimationNodeBlendSpace2DEditor::_update_edited_point_pos() { if (dragging_selected) { pos += drag_ofs; if (snap->is_pressed()) { - pos.x = Math::snapped(pos.x, blend_space->get_snap().x); - pos.y = Math::snapped(pos.y, blend_space->get_snap().y); + pos = pos.snapped(blend_space->get_snap()); } } updating = true; diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 24cb660f7a..55ffbf9477 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -776,16 +776,16 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) { } if (player) { - for (Map<StringName, ProgressBar *>::Element *E = animations.front(); E; E = E->next()) { - Ref<AnimationNodeAnimation> an = blend_tree->get_node(E->key()); + for (const KeyValue<StringName, ProgressBar *> &E : animations) { + Ref<AnimationNodeAnimation> an = blend_tree->get_node(E.key); if (an.is_valid()) { if (player->has_animation(an->get_animation())) { Ref<Animation> anim = player->get_animation(an->get_animation()); if (anim.is_valid()) { - E->get()->set_max(anim->get_length()); + E.value->set_max(anim->get_length()); //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; - StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E->key()) + "/time"; - E->get()->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path)); + StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time"; + E.value->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path)); } } } diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 68b143358a..ea025dad3e 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -298,7 +298,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { autoplay->set_pressed(current == player->get_autoplay()); - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); EditorNode::get_singleton()->update_keying(); _animation_key_editor_seek(timeline_position, false); } @@ -826,7 +826,7 @@ void AnimationPlayerEditor::_update_player() { pin->set_disabled(player == nullptr); if (!player) { - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); EditorNode::get_singleton()->update_keying(); return; } @@ -1466,15 +1466,15 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { } void AnimationPlayerEditor::_start_onion_skinning() { - // FIXME: Using "idle_frame" makes onion layers update one frame behind the current. - if (!get_tree()->is_connected("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { - get_tree()->connect("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); + // FIXME: Using "process_frame" makes onion layers update one frame behind the current. + if (!get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { + get_tree()->connect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); } } void AnimationPlayerEditor::_stop_onion_skinning() { - if (get_tree()->is_connected("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { - get_tree()->disconnect("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); + if (get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { + get_tree()->disconnect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); _free_onion_layers(); diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 0a514d3ff1..eb8db2eaba 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -128,6 +128,7 @@ class AnimationPlayerEditor : public VBoxContainer { bool updating_blends; AnimationTrackEditor *track_editor; + static AnimationPlayerEditor *singleton; // Onion skinning. struct { @@ -226,7 +227,8 @@ protected: public: AnimationPlayer *get_player() const; - static AnimationPlayerEditor *singleton; + + static AnimationPlayerEditor *get_singleton() { return singleton; } bool is_pinned() const { return pin->is_pressed(); } void unpin() { pin->set_pressed(false); } diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index cd84be0c25..6c5606fbfd 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -55,7 +55,7 @@ void AnimationTreeEditor::edit(AnimationTree *p_tree) { tree = p_tree; Vector<String> path; - if (tree->has_meta("_tree_edit_path")) { + if (tree && tree->has_meta("_tree_edit_path")) { path = tree->get_meta("_tree_edit_path"); edit_path(path); } else { diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 664b2f521e..aacfc3e305 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -830,9 +830,9 @@ void EditorAssetLibrary::_update_image_queue() { int current_images = 0; List<int> to_delete; - for (Map<int, ImageQueue>::Element *E = image_queue.front(); E; E = E->next()) { - if (!E->get().active && current_images < max_images) { - String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text()); + for (KeyValue<int, ImageQueue> &E : image_queue) { + if (!E.value.active && current_images < max_images) { + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E.value.image_url.md5_text()); Vector<String> headers; if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) { @@ -844,14 +844,14 @@ void EditorAssetLibrary::_update_image_queue() { } } - Error err = E->get().request->request(E->get().image_url, headers); + Error err = E.value.request->request(E.value.image_url, headers); if (err != OK) { - to_delete.push_back(E->key()); + to_delete.push_back(E.key); } else { - E->get().active = true; + E.value.active = true; } current_images++; - } else if (E->get().active) { + } else if (E.value.active) { current_images++; } } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index faa3b58dbb..8935f715e6 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -513,7 +513,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) { } void CanvasItemEditor::_keying_changed() { - if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree()) { + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->is_visible_in_tree()) { animation_hb->show(); } else { animation_hb->hide(); @@ -748,8 +748,8 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retreive_locked, bool remove_canvas_item_if_parent_in_selection) { List<CanvasItem *> selection; - for (Map<Node *, Object *>::Element *E = editor_selection->get_selection().front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retreive_locked || !_is_node_locked(canvas_item))) { CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); if (se) { @@ -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(); } } @@ -1460,8 +1460,8 @@ bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEven List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; - if (canvas_item->get_filename() != "" && canvas_item != editor->get_edited_scene()) { - editor->open_request(canvas_item->get_filename()); + if (canvas_item->get_scene_file_path() != "" && canvas_item != editor->get_edited_scene()) { + editor->open_request(canvas_item->get_scene_file_path()); return true; } } @@ -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) { @@ -3782,8 +3794,8 @@ void CanvasItemEditor::_notification(int p_what) { } // Update the viewport if bones changes - for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { - Object *b = ObjectDB::get_instance(E->key().from); + for (KeyValue<BoneKey, BoneList> &E : bone_list) { + Object *b = ObjectDB::get_instance(E.key.from); if (!b) { viewport->update(); break; @@ -3796,14 +3808,14 @@ void CanvasItemEditor::_notification(int p_what) { Transform2D global_xform = b2->get_global_transform(); - if (global_xform != E->get().xform) { - E->get().xform = global_xform; + if (global_xform != E.value.xform) { + E.value.xform = global_xform; viewport->update(); } Bone2D *bone = Object::cast_to<Bone2D>(b); - if (bone && bone->get_length() != E->get().length) { - E->get().length = bone->get_length(); + if (bone && bone->get_length() != E.value.length) { + E.value.length = bone->get_length(); viewport->update(); } } @@ -3816,7 +3828,7 @@ void CanvasItemEditor::_notification(int p_what) { select_sb->set_default_margin(Side(i), 4); } - AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); + AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); _keying_changed(); } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { @@ -4254,17 +4266,13 @@ 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) { Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item || !canvas_item->is_visible_in_tree()) { continue; } @@ -4277,13 +4285,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Node2D *n2d = Object::cast_to<Node2D>(canvas_item); if (key_pos && p_location) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); } if (key_rot && p_rotation) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); } if (key_scale && p_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); } if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { @@ -4309,13 +4317,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, if (has_chain && ik_chain.size()) { for (Node2D *&F : ik_chain) { if (key_pos) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); } if (key_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); } } } @@ -4325,13 +4333,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Control *ctrl = Object::cast_to<Control>(canvas_item); if (key_pos) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing); } if (key_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); } } } @@ -4695,8 +4703,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item || !canvas_item->is_visible_in_tree()) { continue; } @@ -4741,8 +4749,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { case ANIM_CLEAR_POSE: { Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item || !canvas_item->is_visible_in_tree()) { continue; } @@ -4771,7 +4779,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { } /* if (key_scale) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); */ } } @@ -4816,8 +4824,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { Node *editor_root = EditorNode::get_singleton()->get_edited_scene()->get_tree()->get_edited_scene_root(); undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)")); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node2D *n2d = Object::cast_to<Node2D>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node2D *n2d = Object::cast_to<Node2D>(E.key); Bone2D *new_bone = memnew(Bone2D); String new_bone_name = n2d->get_name(); @@ -4861,8 +4869,8 @@ void CanvasItemEditor::_focus_selection(int p_op) { int count = 0; Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item) { continue; } @@ -4898,8 +4906,7 @@ void CanvasItemEditor::_focus_selection(int p_op) { if (p_op == VIEW_CENTER_TO_SELECTION) { center = rect.get_center(); Vector2 offset = viewport->get_size() / 2 - editor->get_scene_root()->get_global_canvas_transform().xform(center); - view_offset.x -= Math::round(offset.x / zoom); - view_offset.y -= Math::round(offset.y / zoom); + view_offset -= (offset / zoom).round(); update_viewport(); } else { // VIEW_FRAME_TO_SELECTION @@ -5385,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); @@ -5801,7 +5808,7 @@ void CanvasItemEditorViewport::_remove_preview() { } bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) { - if (p_desired_node->get_filename() == p_target_scene_path) { + if (p_desired_node->get_scene_file_path() == p_target_scene_path) { return true; } @@ -5898,14 +5905,14 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons return false; } - if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) { + if (editor->get_edited_scene()->get_scene_file_path() != "") { // cyclical instancing + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { memdelete(instantiated_scene); return false; } } - instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene()); diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp index 8b354c33a1..53314db1e9 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp @@ -103,16 +103,16 @@ void CollisionPolygon3DEditor::_wip_close() { undo_redo->commit_action(); } -bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!node) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Transform3D gt = node->get_global_transform(); Transform3D gi = gt.affine_inverse(); float depth = _get_depth() * 0.5; Vector3 n = gt.basis.get_axis(2).normalized(); - Plane p(gt.origin + n * depth, n); + Plane p(n, gt.origin + n * depth); Ref<InputEventMouseButton> mb = p_event; @@ -124,7 +124,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector3 spoint; if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } spoint = gi.xform(spoint); @@ -151,19 +151,19 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con snap_ignore = false; _polygon_draw(); edited_point = 1; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { if (wip.size() > 1 && p_camera->unproject_position(gt.xform(Vector3(wip[0].x, wip[0].y, depth))).distance_to(gpoint) < grab_threshold) { //wip closed _wip_close(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { wip.push_back(cpoint); edited_point = wip.size(); snap_ignore = false; _polygon_draw(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) { @@ -184,7 +184,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } //search edges @@ -219,7 +219,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con _polygon_draw(); snap_ignore = true; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } else { //look for points to move @@ -244,7 +244,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con edited_point_pos = poly[closest_idx]; _polygon_draw(); snap_ignore = false; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } else { @@ -253,7 +253,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con if (edited_point != -1) { //apply - ERR_FAIL_INDEX_V(edited_point, poly.size(), false); + ERR_FAIL_INDEX_V(edited_point, poly.size(), EditorPlugin::AFTER_GUI_INPUT_PASS); poly.write[edited_point] = edited_point_pos; undo_redo->create_action(TTR("Edit Poly")); undo_redo->add_do_method(node, "set_polygon", poly); @@ -263,7 +263,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->commit_action(); edited_point = -1; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } @@ -290,7 +290,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } @@ -310,7 +310,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector3 spoint; if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } spoint = gi.xform(spoint); @@ -332,7 +332,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } float CollisionPolygon3DEditor::_get_depth() { diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/collision_polygon_3d_editor_plugin.h index 5db0f7308a..10b0adf76c 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.h +++ b/editor/plugins/collision_polygon_3d_editor_plugin.h @@ -88,7 +88,7 @@ protected: static void _bind_methods(); public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); void edit(Node *p_collision_polygon); CollisionPolygon3DEditor(EditorNode *p_editor); ~CollisionPolygon3DEditor(); @@ -101,7 +101,7 @@ class Polygon3DEditorPlugin : public EditorPlugin { EditorNode *editor; public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } virtual String get_name() const override { return "Polygon3DEditor"; } bool has_main_screen() const override { return false; } diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 903a3689b0..5ac58795d1 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -362,6 +362,7 @@ void GPUParticles3DEditor::_generate_emission_points() { Ref<ImageTexture> tex; tex.instantiate(); + tex->create_from_image(image); Ref<ParticlesMaterial> material = node->get_process_material(); ERR_FAIL_COND(material.is_null()); @@ -390,6 +391,7 @@ void GPUParticles3DEditor::_generate_emission_points() { Ref<ImageTexture> tex2; tex2.instantiate(); + tex2->create_from_image(image2); material->set_emission_normal_texture(tex2); } else { diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp index a4436525fb..6df2e34ceb 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -33,7 +33,7 @@ void GPUParticlesCollisionSDFEditorPlugin::_bake() { if (col_sdf) { if (col_sdf->get_texture().is_null() || !col_sdf->get_texture()->get_path().is_resource_file()) { - String path = get_tree()->get_edited_scene_root()->get_filename(); + String path = get_tree()->get_edited_scene_root()->get_scene_file_path(); if (path == String()) { path = "res://" + col_sdf->get_name() + "_data.exr"; } else { diff --git a/editor/plugins/item_list_editor_plugin.cpp b/editor/plugins/item_list_editor_plugin.cpp index 3207a989bd..16cafda899 100644 --- a/editor/plugins/item_list_editor_plugin.cpp +++ b/editor/plugins/item_list_editor_plugin.cpp @@ -244,6 +244,7 @@ void ItemListEditor::_node_removed(Node *p_node) { void ItemListEditor::_notification(int p_notification) { if (p_notification == NOTIFICATION_ENTER_TREE || p_notification == NOTIFICATION_THEME_CHANGED) { add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + clear_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); del_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); } else if (p_notification == NOTIFICATION_READY) { get_tree()->connect("node_removed", callable_mp(this, &ItemListEditor::_node_removed)); @@ -258,6 +259,12 @@ void ItemListEditor::_add_pressed() { item_plugins[selected_idx]->add_item(); } +void ItemListEditor::_clear_pressed() { + for (int i = item_plugins[selected_idx]->get_item_count() - 1; i >= 0; i--) { + item_plugins[selected_idx]->erase(i); + } +} + void ItemListEditor::_delete_pressed() { if (selected_idx == -1) { return; @@ -350,6 +357,11 @@ ItemListEditor::ItemListEditor() { hbc->add_spacer(); + clear_button = memnew(Button); + clear_button->set_text(TTR("Delete All")); + hbc->add_child(clear_button); + clear_button->connect("pressed", callable_mp(this, &ItemListEditor::_clear_pressed)); + del_button = memnew(Button); del_button->set_text(TTR("Delete")); hbc->add_child(del_button); diff --git a/editor/plugins/item_list_editor_plugin.h b/editor/plugins/item_list_editor_plugin.h index 8c77f3d952..8f61aef083 100644 --- a/editor/plugins/item_list_editor_plugin.h +++ b/editor/plugins/item_list_editor_plugin.h @@ -204,6 +204,7 @@ class ItemListEditor : public HBoxContainer { Tree *tree; Button *add_button; Button *del_button; + Button *clear_button; int selected_idx; @@ -213,6 +214,7 @@ class ItemListEditor : public HBoxContainer { void _add_pressed(); void _delete_pressed(); + void _clear_pressed(); void _node_removed(Node *p_node); diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index b4a70cd31d..123087446c 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -43,9 +43,9 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { switch (err) { case LightmapGI::BAKE_ERROR_NO_SAVE_PATH: { - String scene_path = lightmap->get_filename(); + String scene_path = lightmap->get_scene_file_path(); if (scene_path == String()) { - scene_path = lightmap->get_owner()->get_filename(); + scene_path = lightmap->get_owner()->get_scene_file_path(); } if (scene_path == String()) { EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for lightmap images.\nSave your scene and try again.")); diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index eb771f2bc4..b28f3e134c 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -43,13 +43,13 @@ #include "scene/3d/decal.h" #include "scene/3d/gpu_particles_3d.h" #include "scene/3d/gpu_particles_collision_3d.h" +#include "scene/3d/joint_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/lightmap_gi.h" #include "scene/3d/lightmap_probe.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/occluder_instance_3d.h" -#include "scene/3d/physics_joint_3d.h" #include "scene/3d/position_3d.h" #include "scene/3d/ray_cast_3d.h" #include "scene/3d/reflection_probe.h" @@ -682,7 +682,7 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, } if (collision_segments.size()) { - Plane camp(p_camera->get_transform().origin, (-p_camera->get_transform().basis.get_axis(2)).normalized()); + Plane camp(-p_camera->get_transform().basis.get_axis(2).normalized(), p_camera->get_transform().origin); int vc = collision_segments.size(); const Vector3 *vptr = collision_segments.ptr(); @@ -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); @@ -1313,7 +1313,7 @@ void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, light->set_param(Light3D::PARAM_RANGE, d); } else if (Object::cast_to<OmniLight3D>(light)) { - Plane cp = Plane(gt.origin, p_camera->get_transform().basis.get_axis(2)); + Plane cp = Plane(p_camera->get_transform().basis.get_axis(2), gt.origin); Vector3 inters; if (cp.intersects_ray(ray_from, ray_dir, &inters)) { @@ -1840,47 +1840,6 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(lines, material); p_gizmo->add_handles(handles, get_material("handles")); - - ClippedCamera3D *clipcam = Object::cast_to<ClippedCamera3D>(camera); - if (clipcam) { - Node3D *parent = Object::cast_to<Node3D>(camera->get_parent()); - if (!parent) { - return; - } - Vector3 cam_normal = -camera->get_global_transform().basis.get_axis(Vector3::AXIS_Z).normalized(); - Vector3 cam_x = camera->get_global_transform().basis.get_axis(Vector3::AXIS_X).normalized(); - Vector3 cam_y = camera->get_global_transform().basis.get_axis(Vector3::AXIS_Y).normalized(); - Vector3 cam_pos = camera->get_global_transform().origin; - Vector3 parent_pos = parent->get_global_transform().origin; - - Plane parent_plane(parent_pos, cam_normal); - Vector3 ray_from = parent_plane.project(cam_pos); - - lines.clear(); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5); - - lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5); - lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5); - - lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5); - lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5); - - lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5); - - if (parent_plane.distance_to(cam_pos) < 0) { - lines.push_back(ray_from); - lines.push_back(cam_pos); - } - - Transform3D local = camera->get_global_transform().affine_inverse(); - for (int i = 0; i < lines.size(); i++) { - lines.write[i] = local.xform(lines[i]); - } - - p_gizmo->add_lines(lines, material); - } } ////// @@ -2070,169 +2029,6 @@ void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(cursor_points); } -///// - -Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); - create_material("skeleton_material", gizmo_color); -} - -bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; -} - -String Skeleton3DGizmoPlugin::get_gizmo_name() const { - return "Skeleton3D"; -} - -int Skeleton3DGizmoPlugin::get_priority() const { - return -1; -} - -void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); - - p_gizmo->clear(); - - Ref<Material> material = get_material("skeleton_material", p_gizmo); - - Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); - - surface_tool->begin(Mesh::PRIMITIVE_LINES); - surface_tool->set_material(material); - LocalVector<Transform3D> grests; - grests.resize(skel->get_bone_count()); - - LocalVector<int> bones; - LocalVector<float> weights; - bones.resize(4); - weights.resize(4); - - for (int i = 0; i < 4; i++) { - bones[i] = 0; - weights[i] = 0; - } - - weights[0] = 1; - - AABB aabb; - - Color bonecolor = Color(1.0, 0.4, 0.4, 0.3); - Color rootcolor = Color(0.4, 1.0, 0.4, 0.1); - - LocalVector<int> bones_to_process; - bones_to_process = skel->get_parentless_bones(); - - while (bones_to_process.size() > 0) { - int current_bone_idx = bones_to_process[0]; - bones_to_process.erase(current_bone_idx); - - LocalVector<int> child_bones_vector; - child_bones_vector = skel->get_bone_children(current_bone_idx); - int child_bones_size = child_bones_vector.size(); - - if (skel->get_bone_parent(current_bone_idx) < 0) { - grests[current_bone_idx] = skel->get_bone_rest(current_bone_idx); - } - - for (int i = 0; i < child_bones_size; i++) { - int child_bone_idx = child_bones_vector[i]; - - grests[child_bone_idx] = grests[current_bone_idx] * skel->get_bone_rest(child_bone_idx); - Vector3 v0 = grests[current_bone_idx].origin; - Vector3 v1 = grests[child_bone_idx].origin; - Vector3 d = (v1 - v0).normalized(); - real_t dist = v0.distance_to(v1); - - // Find closest axis. - int closest = -1; - real_t closest_d = 0.0; - for (int j = 0; j < 3; j++) { - real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d)); - if (j == 0 || dp > closest_d) { - closest = j; - } - } - - // Find closest other. - Vector3 first; - Vector3 points[4]; - int point_idx = 0; - for (int j = 0; j < 3; j++) { - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(rootcolor); - surface_tool->add_vertex(v0 - grests[current_bone_idx].basis[j].normalized() * dist * 0.05); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(rootcolor); - surface_tool->add_vertex(v0 + grests[current_bone_idx].basis[j].normalized() * dist * 0.05); - - if (j == closest) { - continue; - } - - Vector3 axis; - if (first == Vector3()) { - axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized(); - first = axis; - } else { - axis = d.cross(first).normalized(); - } - - for (int k = 0; k < 2; k++) { - if (k == 1) { - axis = -axis; - } - Vector3 point = v0 + d * dist * 0.2; - point += axis * dist * 0.1; - - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(v0); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(point); - - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(point); - bones[0] = child_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(v1); - points[point_idx++] = point; - } - } - SWAP(points[1], points[2]); - for (int j = 0; j < 4; j++) { - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(points[j]); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(points[(j + 1) % 4]); - } - - // Add the bone's children to the list of bones to be processed. - bones_to_process.push_back(child_bones_vector[i]); - } - } - - Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>())); -} - //// PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() { @@ -4773,10 +4569,10 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } Vector<Vector3> lines; - for (Map<_EdgeKey, bool>::Element *E = edge_map.front(); E; E = E->next()) { - if (E->get()) { - lines.push_back(E->key().from); - lines.push_back(E->key().to); + for (const KeyValue<_EdgeKey, bool> &E : edge_map) { + if (E.value) { + lines.push_back(E.key.from); + lines.push_back(E.key.to); } } diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index 24b4a23d4b..d1aca4d92e 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -332,18 +332,6 @@ public: Position3DGizmoPlugin(); }; -class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); - -public: - bool has_gizmo(Node3D *p_spatial) override; - String get_gizmo_name() const override; - int get_priority() const override; - void redraw(EditorNode3DGizmo *p_gizmo) override; - - Skeleton3DGizmoPlugin(); -}; - class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index d52a633fed..32a8bbf406 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -357,8 +357,8 @@ int Node3DEditorViewport::get_selected_count() const { int count = 0; - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node3D *sp = Object::cast_to<Node3D>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E.key); if (!sp) { continue; } @@ -651,13 +651,13 @@ void Node3DEditorViewport::_select_region() { Vector3 a = _get_screen_to_space(box[i]); Vector3 b = _get_screen_to_space(box[(i + 1) % 4]); if (orthogonal) { - frustum.push_back(Plane(a, (a - b).normalized())); + frustum.push_back(Plane((a - b).normalized(), a)); } else { frustum.push_back(Plane(a, b, cam_pos)); } } - Plane near(cam_pos, -_get_camera_normal()); + Plane near(-_get_camera_normal(), cam_pos); near.d -= get_znear(); frustum.push_back(near); @@ -864,8 +864,8 @@ void Node3DEditorViewport::_compute_edit(const Point2 &p_point) { Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; if (se && se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - int subgizmo_id = E->key(); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + int subgizmo_id = E.key; se->subgizmos[subgizmo_id] = se->gizmo->get_subgizmo_transform(subgizmo_id); } se->original_local = selected->get_transform(); @@ -972,7 +972,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667); Vector3 r; - Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); if (plane.intersects_ray(ray_pos, ray, &r)) { const real_t dist = r.distance_to(grabber_pos); @@ -1010,7 +1010,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b float col_d = 1e20; for (int i = 0; i < 3; i++) { - Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); Vector3 r; if (!plane.intersects_ray(ray_pos, ray, &r)) { continue; @@ -1076,7 +1076,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667); Vector3 r; - Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); if (plane.intersects_ray(ray_pos, ray, &r)) { const real_t dist = r.distance_to(grabber_pos); @@ -1291,24 +1291,31 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; //do NONE } + EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; { EditorNode *en = editor; EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding(); if (!force_input_forwarding_list->is_empty()) { - bool discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); - if (discard) { + EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); + if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } + if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } } } { EditorNode *en = editor; EditorPluginList *over_plugin_list = en->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { - bool discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); - if (discard) { + EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); + if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } + if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } } } @@ -1374,9 +1381,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector<int> ids; Vector<Transform3D> restore; - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - ids.push_back(GE->key()); - restore.push_back(GE->value()); + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + ids.push_back(GE.key); + restore.push_back(GE.value); } se->gizmo->commit_subgizmos(ids, restore, true); @@ -1573,17 +1580,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { break; } - clicked = _select_ray(b->get_position()); + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + clicked = _select_ray(b->get_position()); - //clicking is always deferred to either move or release + //clicking is always deferred to either move or release - clicked_wants_append = b->is_shift_pressed(); + clicked_wants_append = b->is_shift_pressed(); - if (clicked.is_null()) { - //default to regionselect - cursor.region_select = true; - cursor.region_begin = b->get_position(); - cursor.region_end = b->get_position(); + if (clicked.is_null()) { + //default to regionselect + cursor.region_select = true; + cursor.region_begin = b->get_position(); + cursor.region_end = b->get_position(); + } } surface->update(); @@ -1594,14 +1603,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { break; } - if (clicked.is_valid()) { - _select_clicked(false); - } + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + if (clicked.is_valid()) { + _select_clicked(false); + } - if (cursor.region_select) { - _select_region(); - cursor.region_select = false; - surface->update(); + if (cursor.region_select) { + _select_region(); + cursor.region_select = false; + surface->update(); + } } if (_edit.mode != TRANSFORM_NONE) { @@ -1612,9 +1623,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector<int> ids; Vector<Transform3D> restore; - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - ids.push_back(GE->key()); - restore.push_back(GE->value()); + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + ids.push_back(GE.key); + restore.push_back(GE.value); } se->gizmo->commit_subgizmos(ids, restore, false); @@ -1750,33 +1761,33 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: motion_mask = Vector3(0, 0, 0); - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Y_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Z_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_YZ: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); plane_mv = true; break; case TRANSFORM_XZ: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); plane_mv = true; break; case TRANSFORM_XY: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); plane_mv = true; break; } @@ -1845,13 +1856,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords); if (!local_coords) { new_xform = se->original.affine_inverse() * new_xform; } - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + se->gizmo->set_subgizmo_transform(GE.key, new_xform); } } else { Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords); @@ -1871,30 +1882,30 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Y_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Z_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_YZ: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); plane_mv = true; break; case TRANSFORM_XZ: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); plane_mv = true; break; case TRANSFORM_XY: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); plane_mv = true; break; } @@ -1944,11 +1955,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords); new_xform = se->original.affine_inverse() * new_xform; - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + se->gizmo->set_subgizmo_transform(GE.key, new_xform); } } else { Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords); @@ -1967,18 +1978,18 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); axis = Vector3(1, 0, 0); break; case TRANSFORM_Y_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); axis = Vector3(0, 1, 0); break; case TRANSFORM_Z_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); axis = Vector3(0, 0, 1); break; case TRANSFORM_YZ: @@ -2030,14 +2041,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector3 compute_axis = local_coords ? axis : plane.normal; if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords); if (!local_coords) { new_xform = se->original.affine_inverse() * new_xform; } - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + se->gizmo->set_subgizmo_transform(GE.key, new_xform); } } else { Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords); @@ -2215,6 +2226,21 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("spatial_editor/right_view", p_event)) { _menu_option(VIEW_RIGHT); } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_down", p_event)) { + cursor.x_rot -= Math_PI / 12.0; + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_up", p_event)) { + cursor.x_rot += Math_PI / 12.0; + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_right", p_event)) { + cursor.y_rot -= Math_PI / 12.0; + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_left", p_event)) { + cursor.y_rot += Math_PI / 12.0; + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_180", p_event)) { + cursor.y_rot += Math_PI; + } if (ED_IS_SHORTCUT("spatial_editor/focus_origin", p_event)) { _menu_option(VIEW_CENTER_TO_ORIGIN); } @@ -2237,7 +2263,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } - if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) { + if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) { set_message(TTR("Keying is disabled (no key inserted).")); return; } @@ -2483,8 +2509,14 @@ static bool is_shortcut_pressed(const String &p_path) { if (shortcut.is_null()) { return false; } - InputEventKey *k = Object::cast_to<InputEventKey>(shortcut->get_event().ptr()); - if (k == nullptr) { + + const Array shortcuts = shortcut->get_events(); + Ref<InputEventKey> k; + if (shortcuts.size() > 0) { + k = shortcuts.front(); + } + + if (k.is_null()) { return false; } const Input &input = *Input::get_singleton(); @@ -2601,6 +2633,9 @@ void Node3DEditorViewport::_project_settings_changed() { const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"); viewport->set_use_occlusion_culling(use_occlusion_culling); + + const float lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels"); + viewport->set_lod_threshold(lod_threshold); } void Node3DEditorViewport::_notification(int p_what) { @@ -2663,8 +2698,8 @@ void Node3DEditorViewport::_notification(int p_what) { bool changed = false; bool exist = false; - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node3D *sp = Object::cast_to<Node3D>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E.key); if (!sp) { continue; } @@ -2688,15 +2723,28 @@ void Node3DEditorViewport::_notification(int p_what) { se->aabb = new_aabb; - t.translate(se->aabb.position); + Transform3D t_offset = t; // apply AABB scaling before item's global transform - Basis aabb_s; - aabb_s.scale(se->aabb.size); - t.basis = t.basis * aabb_s; + { + const Vector3 offset(0.005, 0.005, 0.005); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t.translate(se->aabb.position - offset / 2); + t.basis = t.basis * aabb_s; + } + { + const Vector3 offset(0.01, 0.01, 0.01); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t_offset.translate(se->aabb.position - offset / 2); + t_offset.basis = t_offset.basis * aabb_s; + } RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_offset, t_offset); RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray_offset, t_offset); } if (changed || (spatial_editor->is_gizmo_visible() && !exist)) { @@ -3555,7 +3603,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { const Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized(); const Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized(); - const Plane p(camera_xform.origin, camz); + const Plane p = Plane(camz, camera_xform.origin); const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON); const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y; const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y; @@ -3806,8 +3854,8 @@ void Node3DEditorViewport::focus_selection() { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - center += se->gizmo->get_subgizmo_transform(GE->key()).origin; + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + center += se->gizmo->get_subgizmo_transform(GE.key).origin; count++; } } @@ -3830,7 +3878,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); @@ -3917,7 +3965,7 @@ void Node3DEditorViewport::_remove_preview() { } bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) { - if (p_desired_node->get_filename() == p_target_scene_path) { + if (p_desired_node->get_scene_file_path() == p_target_scene_path) { return true; } @@ -3959,15 +4007,15 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po return false; } - if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) { + if (editor->get_edited_scene()->get_scene_file_path() != "") { // cyclical instancing + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { memdelete(instantiated_scene); return false; } } if (scene != nullptr) { - instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); } editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); @@ -4722,9 +4770,15 @@ Node3DEditorSelectedItem::~Node3DEditorSelectedItem() { if (sbox_instance.is_valid()) { RenderingServer::get_singleton()->free(sbox_instance); } + if (sbox_instance_offset.is_valid()) { + RenderingServer::get_singleton()->free(sbox_instance_offset); + } if (sbox_instance_xray.is_valid()) { RenderingServer::get_singleton()->free(sbox_instance_xray); } + if (sbox_instance_xray_offset.is_valid()) { + RenderingServer::get_singleton()->free(sbox_instance_xray_offset); + } } void Node3DEditor::select_gizmo_highlight_axis(int p_axis) { @@ -4747,8 +4801,8 @@ void Node3DEditor::update_transform_gizmo() { Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; if (se && se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E->key()); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E.key); gizmo_center += xf.origin; if (count == 0 && local_gizmo_coords) { gizmo_basis = xf.basis; @@ -4827,23 +4881,39 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { si->sbox_instance = RenderingServer::get_singleton()->instance_create2( selection_box->get_rid(), sp->get_world_3d()->get_scenario()); + si->sbox_instance_offset = RenderingServer::get_singleton()->instance_create2( + selection_box->get_rid(), + sp->get_world_3d()->get_scenario()); RS::get_singleton()->instance_geometry_set_cast_shadows_setting( si->sbox_instance, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting( + si->sbox_instance_offset, + RS::SHADOW_CASTING_SETTING_OFF); // Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting. // It's still possible to approximately guess what is selected by looking at the manipulation gizmo position. RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); + RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2( selection_box_xray->get_rid(), sp->get_world_3d()->get_scenario()); + si->sbox_instance_xray_offset = RenderingServer::get_singleton()->instance_create2( + selection_box_xray->get_rid(), + sp->get_world_3d()->get_scenario()); RS::get_singleton()->instance_geometry_set_cast_shadows_setting( si->sbox_instance_xray, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting( + si->sbox_instance_xray_offset, + RS::SHADOW_CASTING_SETTING_OFF); // Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting. // It's still possible to approximately guess what is selected by looking at the manipulation gizmo position. RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); - RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); return si; } @@ -4851,10 +4921,6 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { void Node3DEditor::_generate_selection_boxes() { // Use two AABBs to create the illusion of a slightly thicker line. AABB aabb(Vector3(), Vector3(1, 1, 1)); - AABB aabb_offset(Vector3(), Vector3(1, 1, 1)); - // Grow the bounding boxes slightly to avoid Z-fighting with the mesh's edges. - aabb.grow_by(0.005); - aabb_offset.grow_by(0.01); // Create a x-ray (visible through solid surfaces) and standard version of the selection box. // Both will be drawn at the same position, but with different opacity. @@ -4874,16 +4940,6 @@ void Node3DEditor::_generate_selection_boxes() { st_xray->add_vertex(b); } - for (int i = 0; i < 12; i++) { - Vector3 a, b; - aabb_offset.get_edge(i, a, b); - - st->add_vertex(a); - st->add_vertex(b); - st_xray->add_vertex(a); - st_xray->add_vertex(b); - } - Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color"); @@ -6130,7 +6186,7 @@ void Node3DEditor::_init_grid() { if (orthogonal) { camera_distance = camera->get_size() / 2.0; Vector3 camera_direction = -camera->get_global_transform().get_basis().get_axis(2); - Plane grid_plane = Plane(Vector3(), normal); + Plane grid_plane = Plane(normal); Vector3 intersection; if (grid_plane.intersects_ray(camera_position, camera_direction, &intersection)) { camera_position = intersection; @@ -6280,7 +6336,7 @@ void Node3DEditor::update_grid() { // Gets a orthogonal or perspective position correctly (for the grid comparison) const Vector3 camera_position = get_editor_viewport(0)->camera->get_position(); - if (!grid_init_draw || (camera_position - grid_camera_last_update_position).length() >= 10.0f) { + if (!grid_init_draw || grid_camera_last_update_position.distance_squared_to(camera_position) >= 100.0f) { _finish_grid(); _init_grid(); grid_init_draw = true; @@ -6428,7 +6484,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; @@ -6444,7 +6500,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; @@ -6674,8 +6730,8 @@ Vector<int> Node3DEditor::get_subgizmo_selection() { Vector<int> ret; if (se) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - ret.push_back(E->key()); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + ret.push_back(E.key); } } return ret; @@ -6727,6 +6783,33 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { } } +void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) { + if (p_id == -1) { + _clear_subgizmo_selection(p_obj); + return; + } + + Node3D *sp = nullptr; + if (p_obj) { + sp = Object::cast_to<Node3D>(p_obj); + } else { + sp = selected; + } + + if (!sp) { + return; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (se) { + se->subgizmos.clear(); + se->subgizmos.insert(p_id, p_transform); + se->gizmo = p_gizmo; + sp->update_gizmos(); + update_transform_gizmo(); + } +} + void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) { Node3D *sp = nullptr; if (p_obj) { @@ -6852,7 +6935,6 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin))); add_gizmo_plugin(Ref<SoftDynamicBody3DGizmoPlugin>(memnew(SoftDynamicBody3DGizmoPlugin))); add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin))); - add_gizmo_plugin(Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin))); add_gizmo_plugin(Ref<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin))); add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin))); @@ -6877,6 +6959,7 @@ void Node3DEditor::_register_all_gizmos() { void Node3DEditor::_bind_methods() { ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data); ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo); + ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection); ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection); ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons); @@ -7238,6 +7321,11 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { ED_SHORTCUT("spatial_editor/front_view", TTR("Front View"), KEY_KP_1); ED_SHORTCUT("spatial_editor/left_view", TTR("Left View"), KEY_MASK_ALT + KEY_KP_3); ED_SHORTCUT("spatial_editor/right_view", TTR("Right View"), KEY_KP_3); + ED_SHORTCUT("spatial_editor/orbit_view_down", TTR("Orbit View Down"), KEY_KP_2); + ED_SHORTCUT("spatial_editor/orbit_view_left", TTR("Orbit View Left"), KEY_KP_4); + ED_SHORTCUT("spatial_editor/orbit_view_right", TTR("Orbit View Right"), KEY_KP_6); + ED_SHORTCUT("spatial_editor/orbit_view_up", TTR("Orbit View Up"), KEY_KP_8); + ED_SHORTCUT("spatial_editor/orbit_view_180", TTR("Orbit View 180"), KEY_KP_9); ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTR("Switch Perspective/Orthogonal View"), KEY_KP_5); ED_SHORTCUT("spatial_editor/insert_anim_key", TTR("Insert Animation Key"), KEY_K); ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), KEY_O); @@ -7703,6 +7791,13 @@ Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const { return p_target; } +bool Node3DEditor::is_gizmo_visible() const { + if (selected) { + return gizmo.visible && selected->is_transform_gizmo_visible(); + } + return gizmo.visible; +} + double Node3DEditor::get_translate_snap() const { double snap_value; if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 59f3ec6fcd..2d5aeaa981 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -431,7 +431,9 @@ public: bool last_xform_dirty; Node3D *sp; RID sbox_instance; + RID sbox_instance_offset; RID sbox_instance_xray; + RID sbox_instance_xray_offset; Ref<EditorNode3DGizmo> gizmo; Map<int, Transform3D> subgizmos; // map ID -> initial transform @@ -663,6 +665,7 @@ private: Node3D *selected; void _request_gizmo(Object *p_obj); + void _set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D()); void _clear_subgizmo_selection(Object *p_obj = nullptr); static Node3DEditor *singleton; @@ -756,7 +759,7 @@ public: float get_fov() const { return settings_fov->get_value(); } Transform3D get_gizmo_transform() const { return gizmo.transform; } - bool is_gizmo_visible() const { return gizmo.visible; } + bool is_gizmo_visible() const; ToolMode get_tool_mode() const { return tool_mode; } bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); } diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp index ab88b9f00d..0328b1bea6 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -41,9 +41,9 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { switch (err) { case OccluderInstance3D::BAKE_ERROR_NO_SAVE_PATH: { - String scene_path = occluder_instance->get_filename(); + String scene_path = occluder_instance->get_scene_file_path(); if (scene_path == String()) { - scene_path = occluder_instance->get_owner()->get_filename(); + scene_path = occluder_instance->get_owner()->get_scene_file_path(); } if (scene_path == String()) { EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for the occluder.\nSave your scene and try again.")); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 13f7908170..e2902feba1 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -101,7 +101,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point // Setting curve point positions if (p_id < c->get_point_count()) { - Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2)); + const Plane p = Plane(p_camera->get_transform().basis.get_axis(2), gt.xform(original)); Vector3 inters; @@ -125,7 +125,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point Vector3 base = c->get_point_position(idx); - Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2)); + Plane p(p_camera->get_transform().basis.get_axis(2), gt.xform(original)); Vector3 inters; @@ -294,13 +294,13 @@ Path3DGizmo::Path3DGizmo(Path3D *p_path) { orig_out_length = 0; } -bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!path) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Transform3D gt = path->get_global_transform(); Transform3D it = gt.affine_inverse(); @@ -329,14 +329,14 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref const Vector3 *r = v3a.ptr(); if (p_camera->unproject_position(gt.xform(c->get_point_position(0))).distance_to(mbpos) < click_dist) { - return false; //nope, existing + return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing } for (int i = 0; i < c->get_point_count() - 1; i++) { //find the offset and point index of the place to break up int j = idx; if (p_camera->unproject_position(gt.xform(c->get_point_position(i + 1))).distance_to(mbpos) < click_dist) { - return false; //nope, existing + return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing } while (j < rc && c->get_point_position(i + 1) != r[j]) { @@ -386,16 +386,16 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "add_point", closest_seg_point, Vector3(), Vector3(), closest_seg + 1); ur->add_undo_method(c.ptr(), "remove_point", closest_seg + 1); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { - Vector3 org; + Vector3 origin; if (c->get_point_count() == 0) { - org = path->get_transform().get_origin(); + origin = path->get_transform().get_origin(); } else { - org = gt.xform(c->get_point_position(c->get_point_count() - 1)); + origin = gt.xform(c->get_point_position(c->get_point_count() - 1)); } - Plane p(org, p_camera->get_transform().basis.get_axis(2)); + Plane p(p_camera->get_transform().basis.get_axis(2), origin); Vector3 ray_from = p_camera->project_ray_origin(mbpos); Vector3 ray_dir = p_camera->project_ray_normal(mbpos); @@ -405,7 +405,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "add_point", it.xform(inters), Vector3(), Vector3(), -1); ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count()); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } //add new at pos @@ -425,27 +425,27 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "remove_point", i); ur->add_undo_method(c.ptr(), "add_point", c->get_point_position(i), c->get_point_in(i), c->get_point_out(i), i); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_out < click_dist) { UndoRedo *ur = editor->get_undo_redo(); ur->create_action(TTR("Remove Out-Control Point")); ur->add_do_method(c.ptr(), "set_point_out", i, Vector3()); ur->add_undo_method(c.ptr(), "set_point_out", i, c->get_point_out(i)); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_in < click_dist) { UndoRedo *ur = editor->get_undo_redo(); ur->create_action(TTR("Remove In-Control Point")); ur->add_do_method(c.ptr(), "set_point_in", i, Vector3()); ur->add_undo_method(c.ptr(), "set_point_in", i, c->get_point_in(i)); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } void Path3DEditorPlugin::edit(Object *p_object) { @@ -510,7 +510,14 @@ void Path3DEditorPlugin::_close_curve() { if (c->get_point_count() < 2) { return; } - c->add_point(c->get_point_position(0), c->get_point_in(0), c->get_point_out(0)); + if (c->get_point_position(0) == c->get_point_position(c->get_point_count() - 1)) { + return; + } + UndoRedo *ur = editor->get_undo_redo(); + ur->create_action(TTR("Close Curve")); + ur->add_do_method(c.ptr(), "add_point", c->get_point_position(0), c->get_point_in(0), c->get_point_out(0), -1); + ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count()); + ur->commit_action(); } void Path3DEditorPlugin::_handle_option_pressed(int p_option) { diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index b74d7cc59e..974234ba8f 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -100,7 +100,7 @@ public: Path3D *get_edited_path() { return path; } static Path3DEditorPlugin *singleton; - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; virtual String get_name() const override { return "Path3D"; } bool has_main_screen() const override { return false; } diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index f92f50f826..b1e104e680 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -43,6 +43,7 @@ void PhysicalBone3DEditor::_on_toggle_button_transform_joint(bool p_is_pressed) void PhysicalBone3DEditor::_set_move_joint() { if (selected) { selected->_set_gizmo_move_joint(button_transform_joint->is_pressed()); + Node3DEditor::get_singleton()->update_transform_gizmo(); } } diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index ac18ff9f1c..5afe9ed60c 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -779,7 +779,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { if (mm.is_valid()) { if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) || Input::get_singleton()->is_key_pressed(KEY_SPACE)) { - Vector2 drag(mm->get_relative().x, mm->get_relative().y); + Vector2 drag = mm->get_relative(); uv_hscroll->set_value(uv_hscroll->get_value() - drag.x); uv_vscroll->set_value(uv_vscroll->get_value() - drag.y); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 07e4d4ebf0..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")); @@ -484,14 +485,32 @@ void ScriptEditor::_clear_execution(REF p_script) { void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { Ref<Script> script = Object::cast_to<Script>(*p_script); if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { - if (edit(p_script, p_line, 0, false)) { - editor->push_item(p_script.ptr()); - - ScriptEditorBase *se = _get_current_editor(); - if (se) { + // Update if open. + for (int i = 0; i < tab_container->get_child_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + if (se && se->get_edited_resource()->get_path() == script->get_path()) { se->set_breakpoint(p_line, p_enabled); + return; + } + } + + // Handle closed. + Dictionary state = script_editor_cache->get_value(script->get_path(), "state"); + Array breakpoints; + if (state.has("breakpoints")) { + breakpoints = state["breakpoints"]; + } + + if (breakpoints.has(p_line)) { + if (!p_enabled) { + breakpoints.erase(p_line); } + } else if (p_enabled) { + breakpoints.push_back(p_line); } + state["breakpoints"] = breakpoints; + script_editor_cache->set_value(script->get_path(), "state", state); + EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_line + 1, false); } } @@ -502,6 +521,34 @@ void ScriptEditor::_clear_breakpoints() { se->clear_breakpoints(); } } + + // Clear from closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false); + } + + if (breakpoints.size() > 0) { + Dictionary state = script_editor_cache->get_value(E, "state"); + state["breakpoints"] = Array(); + script_editor_cache->set_value(E, "state", state); + } + } +} + +Array ScriptEditor::_get_cached_breakpoints_for_script(const String &p_path) const { + if (!ResourceLoader::exists(p_path, "Script") || p_path.begins_with("local://") || !script_editor_cache->has_section_key(p_path, "state")) { + return Array(); + } + + Dictionary state = script_editor_cache->get_value(p_path, "state"); + if (!state.has("breakpoints")) { + return Array(); + } + return state["breakpoints"]; } ScriptEditorBase *ScriptEditor::_get_current_editor() const { @@ -716,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); } } @@ -756,6 +807,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { int idx = tab_container->get_current_tab(); if (current) { current->clear_edit_menu(); + _save_editor_state(current); } memdelete(tselected); if (idx >= tab_container->get_child_count()) { @@ -1030,35 +1082,21 @@ void ScriptEditor::_file_dialog_action(String p_file) { } file->close(); memdelete(file); - [[fallthrough]]; - } - case FILE_OPEN: { - List<String> extensions; - ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); - if (extensions.find(p_file.get_extension())) { - Ref<Script> scr = ResourceLoader::load(p_file); - if (!scr.is_valid()) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); - file_dialog_option = -1; - return; - } - - edit(scr); - file_dialog_option = -1; - return; - } - Error error; - Ref<TextFile> text_file = _load_text_file(p_file, &error); - if (error != OK) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + if (EditorFileSystem::get_singleton()) { + if (textfile_extensions.has(p_file.get_extension())) { + EditorFileSystem::get_singleton()->update_file(p_file); + } } - if (text_file.is_valid()) { - edit(text_file); - file_dialog_option = -1; + if (!open_textfile_after_create) { return; } + [[fallthrough]]; + } + case FILE_OPEN: { + open_file(p_file); + file_dialog_option = -1; } break; case FILE_SAVE_AS: { ScriptEditorBase *current = _get_current_editor(); @@ -1133,8 +1171,12 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog_option = FILE_NEW_TEXTFILE; file_dialog->clear_filters(); + 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...")); + open_textfile_after_create = true; } break; case FILE_OPEN: { file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); @@ -1148,6 +1190,10 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[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("Open File")); return; @@ -1488,6 +1534,7 @@ void ScriptEditor::_notification(int p_what) { editor->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); editor->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); editor->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); + editor->get_filesystem_dock()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); editor->get_filesystem_dock()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); @@ -1497,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: @@ -1600,6 +1648,7 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { } void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { + Set<String> loaded_scripts; for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (!se) { @@ -1612,6 +1661,7 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { } String base = script->get_path(); + loaded_scripts.insert(base); if (base.begins_with("local://") || base == "") { continue; } @@ -1621,6 +1671,20 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1)); } } + + // Load breakpoints that are in closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1)); + } + } } void ScriptEditor::_members_overview_selected(int p_idx) { @@ -2056,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; } @@ -2278,6 +2342,10 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra _add_recent_script(p_resource->get_path()); } + if (script_editor_cache->has_section(p_resource->get_path())) { + se->set_edit_state(script_editor_cache->get_value(p_resource->get_path(), "state")); + } + _sort_list_on_update = true; _update_script_names(); _save_layout(); @@ -2436,6 +2504,41 @@ void ScriptEditor::open_script_create_dialog(const String &p_base_name, const St script_create_dialog->config(p_base_name, p_base_path); } +void ScriptEditor::open_text_file_create_dialog(const String &p_base_path, const String &p_base_name) { + file_dialog->set_current_file(p_base_name); + file_dialog->set_current_dir(p_base_path); + _menu_option(FILE_NEW_TEXTFILE); + open_textfile_after_create = false; +} + +RES ScriptEditor::open_file(const String &p_file) { + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + if (extensions.find(p_file.get_extension())) { + Ref<Script> scr = ResourceLoader::load(p_file); + if (!scr.is_valid()) { + editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + return RES(); + } + + edit(scr); + return scr; + } + + Error error; + Ref<TextFile> text_file = _load_text_file(p_file, &error); + if (error != OK) { + editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + return RES(); + } + + if (text_file.is_valid()) { + edit(text_file); + return text_file; + } + return RES(); +} + void ScriptEditor::_editor_stop() { for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); @@ -2478,6 +2581,20 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const } } +void ScriptEditor::_save_editor_state(ScriptEditorBase *p_editor) { + if (restoring_layout) { + return; + } + + const String &path = p_editor->get_edited_resource()->get_path(); + if (!path.is_resource_file()) { + return; + } + + script_editor_cache->set_value(path, "state", p_editor->get_edit_state()); + // This is saved later when we save the editor layout. +} + void ScriptEditor::_save_layout() { if (restoring_layout) { return; @@ -2487,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"); @@ -2523,6 +2646,26 @@ void ScriptEditor::_filesystem_changed() { _update_script_names(); } +void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_file) { + if (!script_editor_cache->has_section(p_old_file)) { + return; + } + Variant state = script_editor_cache->get_value(p_old_file, "state"); + script_editor_cache->erase_section(p_old_file); + script_editor_cache->set_value(p_new_file, "state", state); + + // If Script, update breakpoints with debugger. + Array breakpoints = _get_cached_breakpoints_for_script(p_new_file); + for (int i = 0; i < breakpoints.size(); i++) { + int line = (int)breakpoints[i] + 1; + EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false); + if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true); + } + } + // This is saved later when we save the editor layout. +} + void ScriptEditor::_file_removed(const String &p_removed_file) { for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); @@ -2534,6 +2677,15 @@ void ScriptEditor::_file_removed(const String &p_removed_file) { _close_tab(i, false, false); } } + + // Check closed. + if (script_editor_cache->has_section(p_removed_file)) { + Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false); + } + script_editor_cache->erase_section(p_removed_file); + } } void ScriptEditor::_update_find_replace_bar() { @@ -2664,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; @@ -2734,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(); @@ -2750,6 +2916,29 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } } +void ScriptEditor::input(const Ref<InputEvent> &p_event) { + // This is implemented in `input()` rather than `unhandled_input()` to allow + // the shortcut to be used regardless of the click location. + // This feature can be disabled to avoid interfering with other uses of the additional + // mouse buttons, such as push-to-talk in a VoIP program. + if (EDITOR_GET("interface/editor/mouse_extra_buttons_navigate_history")) { + const Ref<InputEventMouseButton> mb = p_event; + + // Navigate the script history using additional mouse buttons present on some mice. + // This must be hardcoded as the editor shortcuts dialog doesn't allow assigning + // more than one shortcut per action. + if (mb.is_valid() && mb->is_pressed() && is_visible_in_tree()) { + if (mb->get_button_index() == MOUSE_BUTTON_XBUTTON1) { + _history_back(); + } + + if (mb->get_button_index() == MOUSE_BUTTON_XBUTTON2) { + _history_forward(); + } + } + } +} + void ScriptEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -2861,6 +3050,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = true; + Set<String> loaded_scripts; List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); @@ -2873,8 +3063,12 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { } if (!FileAccess::exists(path)) { + if (script_editor_cache->has_section(path)) { + script_editor_cache->erase_section(path); + } continue; } + loaded_scripts.insert(path); if (extensions.find(path.get_extension())) { Ref<Script> scr = ResourceLoader::load(path); @@ -2919,6 +3113,26 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { script_split->set_split_offset(p_layout->get_value("ScriptEditor", "split_offset")); } + // Remove any deleted editors that have been removed between launches. + // and if a Script, register breakpoints with the debugger. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + if (!FileAccess::exists(E)) { + script_editor_cache->erase_section(E); + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true); + } + } + restoring_layout = false; _update_script_names(); @@ -2936,11 +3150,8 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { continue; } - Dictionary script_info; - script_info["path"] = path; - script_info["state"] = se->get_edit_state(); - - scripts.push_back(script_info); + _save_editor_state(se); + scripts.push_back(path); } EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); @@ -2953,6 +3164,9 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { p_layout->set_value("ScriptEditor", "open_scripts", scripts); p_layout->set_value("ScriptEditor", "open_help", helps); p_layout->set_value("ScriptEditor", "split_offset", script_split->get_split_offset()); + + // Save the cache. + script_editor_cache->save(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); } void ScriptEditor::_help_class_open(const String &p_class) { @@ -3294,7 +3508,6 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_update_script_connections", &ScriptEditor::_update_script_connections); ClassDB::bind_method("_help_class_open", &ScriptEditor::_help_class_open); ClassDB::bind_method("_live_auto_reload_running_scripts", &ScriptEditor::_live_auto_reload_running_scripts); - ClassDB::bind_method("_update_members_overview", &ScriptEditor::_update_members_overview); ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); @@ -3320,6 +3533,9 @@ void ScriptEditor::_bind_methods() { ScriptEditor::ScriptEditor(EditorNode *p_editor) { current_theme = ""; + script_editor_cache.instantiate(); + script_editor_cache->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); + completion_cache = memnew(EditorScriptCodeCompletionCache); restoring_layout = false; waiting_update_names = false; @@ -3427,9 +3643,11 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { ED_SHORTCUT("script_editor/window_sort", TTR("Sort")); ED_SHORTCUT("script_editor/window_move_up", TTR("Move Up"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_UP); ED_SHORTCUT("script_editor/window_move_down", TTR("Move Down"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_DOWN); - ED_SHORTCUT("script_editor/next_script", TTR("Next Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_PERIOD); // these should be KEY_GREATER and KEY_LESS but those don't work + // FIXME: These should be `KEY_GREATER` and `KEY_LESS` but those don't work. + ED_SHORTCUT("script_editor/next_script", TTR("Next Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_PERIOD); ED_SHORTCUT("script_editor/prev_script", TTR("Previous Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_COMMA); - set_process_unhandled_key_input(true); + set_process_input(true); + set_process_unhandled_input(true); file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 7620605570..8caebc1c8c 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -366,6 +366,7 @@ class ScriptEditor : public PanelContainer { void _add_callback(Object *p_obj, const String &p_function, const PackedStringArray &p_args); void _res_saved_callback(const Ref<Resource> &p_res); + bool open_textfile_after_create = true; bool trim_trailing_whitespace_on_save; bool use_space_indentation; bool convert_indent_on_save; @@ -381,13 +382,17 @@ class ScriptEditor : public PanelContainer { void _script_created(Ref<Script> p_script); void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled); void _clear_breakpoints(); + Array _get_cached_breakpoints_for_script(const String &p_path) const; ScriptEditorBase *_get_current_editor() const; Array _get_open_script_editors() const; + Ref<ConfigFile> script_editor_cache; + void _save_editor_state(ScriptEditorBase *p_editor); void _save_layout(); void _editor_settings_changed(); void _filesystem_changed(); + void _files_moved(const String &p_old_file, const String &p_new_file); void _file_removed(const String &p_file); void _autosave_scripts(); void _update_autosave_timer(); @@ -418,6 +423,7 @@ class ScriptEditor : public PanelContainer { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + virtual void input(const Ref<InputEvent> &p_event) override; virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; void _script_list_gui_input(const Ref<InputEvent> &ev); @@ -447,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); @@ -471,6 +478,8 @@ public: bool is_scripts_panel_toggled(); void apply_scripts() const; void open_script_create_dialog(const String &p_base_name, const String &p_base_path); + void open_text_file_create_dialog(const String &p_base_path, const String &p_base_name = ""); + RES open_file(const String &p_file); void ensure_select_current(); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 2b1ca068ee..2c02389db2 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -433,10 +433,12 @@ void ScriptTextEditor::_validate_script() { int warning_nb = warnings.size(); warnings_panel->clear(); + bool has_connections_table = false; // Add missing connections. if (GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { Node *base = get_tree()->get_edited_scene_root(); if (base && missing_connections.size() > 0) { + has_connections_table = true; warnings_panel->push_table(1); for (const Connection &connection : missing_connections) { String base_path = base->get_name(); @@ -458,6 +460,10 @@ void ScriptTextEditor::_validate_script() { code_editor->set_error_count(errors.size()); code_editor->set_warning_count(warning_nb); + if (has_connections_table) { + warnings_panel->add_newline(); + } + // Add script warnings. warnings_panel->push_table(3); for (const ScriptLanguage::Warning &w : warnings) { @@ -1335,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() { @@ -1701,7 +1705,6 @@ void ScriptTextEditor::_enable_code_editor() { code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel)); code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script)); code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings)); - code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); code_editor->get_text_editor()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol)); code_editor->get_text_editor()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol)); code_editor->get_text_editor()->connect("gutter_added", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes)); @@ -1841,6 +1844,7 @@ ScriptTextEditor::ScriptTextEditor() { code_editor->get_text_editor()->set_draw_breakpoints_gutter(true); code_editor->get_text_editor()->set_draw_executing_lines_gutter(true); + code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); connection_gutter = 1; code_editor->get_text_editor()->add_gutter(connection_gutter); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 22ca5592bd..a88e24c0d0 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -131,9 +131,9 @@ void ShaderTextEditor::_load_theme_settings() { List<String> built_ins; if (shader.is_valid()) { - for (const Map<StringName, ShaderLanguage::FunctionInfo>::Element *E = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())).front(); E; E = E->next()) { - for (const Map<StringName, ShaderLanguage::BuiltInInfo>::Element *F = E->get().built_ins.front(); F; F = F->next()) { - built_ins.push_back(F->key()); + for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) { + for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { + built_ins.push_back(F.key); } } diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 309821b3dc..708eaf2c46 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -37,298 +37,251 @@ #include "editor/plugins/animation_player_editor_plugin.h" #include "node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" +#include "scene/3d/joint_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" -#include "scene/3d/physics_joint_3d.h" #include "scene/resources/capsule_shape_3d.h" #include "scene/resources/sphere_shape_3d.h" +#include "scene/resources/surface_tool.h" void BoneTransformEditor::create_editors() { const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); section = memnew(EditorInspectorSection); section->setup("trf_properties", label, this, section_color, true); + section->unfold(); add_child(section); - 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); - - 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); - // Translation property - translation_property = memnew(EditorPropertyVector3()); - translation_property->setup(-10000, 10000, 0.001f, true); - translation_property->set_label("Translation"); - translation_property->set_use_folding(true); - translation_property->set_read_only(false); - translation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); - section->get_vbox()->add_child(translation_property); - - // Rotation property - rotation_property = memnew(EditorPropertyVector3()); + // 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(EditorPropertyQuaternion()); rotation_property->setup(-10000, 10000, 0.001f, true); - rotation_property->set_label("Rotation Degrees"); - rotation_property->set_use_folding(true); - rotation_property->set_read_only(false); - rotation_property->connect("property_changed", 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. scale_property = memnew(EditorPropertyVector3()); scale_property->setup(-10000, 10000, 0.001f, true); scale_property->set_label("Scale"); - scale_property->set_use_folding(true); - scale_property->set_read_only(false); - scale_property->connect("property_changed", 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); + // Transform/Matrix 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->set_read_only(false); - transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform)); - transform_section->get_vbox()->add_child(transform_property); + // Transform/Matrix 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("toggled", callable_mp(this, &BoneTransformEditor::_checkbox_toggled)); - [[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); + if (skeleton) { + undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + 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::_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); +BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : + skeleton(p_skeleton) { + undo_redo = EditorNode::get_undo_redo(); } -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); +void BoneTransformEditor::set_target(const String &p_prop) { + enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled"); + enabled_checkbox->update_property(); - return Transform3D( - Basis(prop_rotation, scale_property->get_vector()), - translation_property->get_vector()); -} + position_property->set_object_and_property(skeleton, p_prop + "position"); + position_property->update_property(); -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); -} + rotation_property->set_object_and_property(skeleton, p_prop + "rotation"); + rotation_property->update_property(); -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") { - undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); - undo_redo->add_undo_property(skeleton, property, skeleton->get(property)); - undo_redo->add_do_property(skeleton, property, p_new_transform); - undo_redo->commit_action(); - } -} + scale_property->set_object_and_property(skeleton, p_prop + "scale"); + scale_property->update_property(); -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); - } + rest_matrix->set_object_and_property(skeleton, p_prop + "rest"); + rest_matrix->update_property(); } void BoneTransformEditor::_update_properties() { - if (updating) { + if (!skeleton) { return; } - - if (skeleton == nullptr) { - return; + 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(); + } + } + } } +} - updating = true; +Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr; - Transform3D tform = skeleton->get(property); - _update_transform_properties(tform); -} +void Skeleton3DEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS, !p_keyable); + skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS_EXISTED, !p_keyable); +}; -void BoneTransformEditor::_update_custom_pose_properties() { - if (updating) { - return; - } +void Skeleton3DEditor::set_rest_options_enabled(const bool p_rest_options_enabled) { + rest_options->get_popup()->set_item_disabled(REST_OPTION_POSE_TO_REST, !p_rest_options_enabled); +}; - if (skeleton == nullptr) { +void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { + 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(); + switch (p_skeleton_option) { + case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: { + create_physical_skeleton(); + break; + } + case SKELETON_OPTION_INIT_POSE: { + init_pose(); + break; + } + case SKELETON_OPTION_INSERT_KEYS: { + insert_keys(true); + break; + } + case SKELETON_OPTION_INSERT_KEYS_EXISTED: { + insert_keys(false); + break; + } + } } -void BoneTransformEditor::set_target(const String &p_prop) { - property = p_prop; -} +void Skeleton3DEditor::_on_click_rest_option(int p_rest_option) { + if (!skeleton) { + return; + } -void BoneTransformEditor::set_keyable(const bool p_keyable) { - keyable = p_keyable; - if (key_button) { - key_button->set_visible(p_keyable); + switch (p_rest_option) { + case REST_OPTION_POSE_TO_REST: { + pose_to_rest(); + break; + } } } -void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { - toggle_enabled = p_enabled; - if (enabled_checkbox) { - enabled_checkbox->set_visible(p_enabled); +void Skeleton3DEditor::init_pose() { + const int bone_len = skeleton->get_bone_count(); + if (!bone_len) { + return; + } + 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++) { + 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(); } -void BoneTransformEditor::_key_button_pressed() { - if (skeleton == nullptr) { +void Skeleton3DEditor::insert_keys(bool p_all_bones) { + if (!skeleton) { return; } - const BoneId bone_id = property.get_slicec('/', 1).to_int(); - const String name = skeleton->get_bone_name(bone_id); + int bone_len = skeleton->get_bone_count(); + Node *root = EditorNode::get_singleton()->get_tree()->get_root(); + String path = root->get_path_to(skeleton); - if (name.is_empty()) { - return; - } + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + te->make_insert_queue(); + for (int i = 0; i < bone_len; i++) { + const String name = skeleton->get_bone_name(i); - // Need to normalize the basis before you key it - Transform3D tform = compute_transform_from_vector3s(); - tform.orthonormalize(); - AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); -} + if (name.is_empty()) { + continue; + } + + if (!p_all_bones && !te->has_transform_track(skeleton, name)) { + continue; + } -void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) { - if (enabled_checkbox) { - const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; - skeleton->set(path, p_toggled); + Transform3D tform = skeleton->get_bone_pose(i); + te->insert_transform_key(skeleton, name, tform); } + te->commit_insert_queue(); } -void Skeleton3DEditor::_on_click_option(int p_option) { +void Skeleton3DEditor::pose_to_rest() { if (!skeleton) { return; } - switch (p_option) { - case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { - create_physical_skeleton(); - break; - } - } + // Todo: Do method with multiple bone selection. + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + 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(); } void Skeleton3DEditor::create_physical_skeleton() { @@ -356,7 +309,7 @@ void Skeleton3DEditor::create_physical_skeleton() { bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); - /// create physical bone on parent + // Create physical bone on parent. if (!bones_infos[parent].physical_bone) { bones_infos.write[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos); @@ -370,7 +323,7 @@ void Skeleton3DEditor::create_physical_skeleton() { bones_infos[parent].physical_bone->set_owner(owner); bones_infos[parent].physical_bone->get_child(0)->set_owner(owner); // set shape owner - /// Create joint between parent of parent + // Create joint between parent of parent. if (-1 != parent_parent) { bones_infos[parent].physical_bone->set_joint_type(PhysicalBone3D::JOINT_TYPE_PIN); } @@ -483,7 +436,7 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se ERR_FAIL_NULL(skeleton); UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Set Bone Parentage")); - // If the target is a child of ourselves, we move only *us* and not our children + // If the target is a child of ourselves, we move only *us* and not our children. if (skeleton->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { const BoneId parent_idx = skeleton->get_bone_parent(p_selected_boneidx); const int bone_count = skeleton->get_bone_count(); @@ -505,43 +458,38 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se void Skeleton3DEditor::_joint_tree_selection_changed() { TreeItem *selected = joint_tree->get_selected(); - const String path = selected->get_metadata(0); - - if (path.begins_with("bones/")) { - const int b_idx = path.get_slicec('/', 1).to_int(); - const String bone_path = "bones/" + itos(b_idx) + "/"; + if (selected) { + const String path = selected->get_metadata(0); - pose_editor->set_target(bone_path + "pose"); - rest_editor->set_target(bone_path + "rest"); - custom_pose_editor->set_target(bone_path + "custom_pose"); + if (path.begins_with("bones/")) { + const int b_idx = path.get_slicec('/', 1).to_int(); + const String bone_path = "bones/" + itos(b_idx) + "/"; - _update_properties(); - - 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_gizmo_visible(); } +// May be not used with single select mode. 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(); - } + Node3DEditor::get_singleton()->update_transform_gizmo(); } void Skeleton3DEditor::update_joint_tree() { joint_tree->clear(); - if (skeleton == nullptr) { + if (!skeleton) { return; } @@ -569,7 +517,7 @@ void Skeleton3DEditor::update_joint_tree() { joint_item->set_selectable(0, true); joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); - // Add the bone's children to the list of bones to be processed + // Add the bone's children to the list of bones to be processed. Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); int child_bone_size = current_bone_child_bones.size(); for (int i = 0; i < child_bone_size; i++) { @@ -587,16 +535,56 @@ void Skeleton3DEditor::create_editors() { set_focus_mode(FOCUS_ALL); - // Create Top Menu Bar - options = memnew(MenuButton); - Node3DEditor::get_singleton()->add_control_to_menu_panel(options); + Node3DEditor *ne = Node3DEditor::get_singleton(); + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + + // Create Top Menu Bar. + separator = memnew(VSeparator); + ne->add_control_to_menu_panel(separator); + + // Create Skeleton Option in Top Menu Bar. + skeleton_options = memnew(MenuButton); + ne->add_control_to_menu_panel(skeleton_options); + + skeleton_options->set_text(TTR("Skeleton3D")); + skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + + skeleton_options->get_popup()->add_item(TTR("Init pose"), SKELETON_OPTION_INIT_POSE); + skeleton_options->get_popup()->add_item(TTR("Insert key of all bone poses"), SKELETON_OPTION_INSERT_KEYS); + skeleton_options->get_popup()->add_item(TTR("Insert key of bone poses already exist track"), SKELETON_OPTION_INSERT_KEYS_EXISTED); + skeleton_options->get_popup()->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON); + + skeleton_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option)); + + // Create Rest Option in Top Menu Bar. + rest_options = memnew(MenuButton); + ne->add_control_to_menu_panel(rest_options); + + rest_options->set_text(TTR("Edit Rest")); + rest_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); + + rest_options->get_popup()->add_item(TTR("Apply current pose to rest"), REST_OPTION_POSE_TO_REST); + rest_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_rest_option)); + set_rest_options_enabled(false); - options->set_text(TTR("Skeleton3D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + Vector<Variant> button_binds; + button_binds.resize(1); - options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); + edit_mode_button = memnew(Button); + ne->add_control_to_menu_panel(edit_mode_button); + edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints.")); + edit_mode_button->set_toggle_mode(true); + edit_mode_button->set_flat(true); + edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); - options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option)); + edit_mode = false; + + set_keyable(te->has_keying()); + + if (skeleton) { + skeleton->add_child(handles_mesh_instance); + handles_mesh_instance->set_skeleton_path(NodePath("")); + } const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); @@ -612,7 +600,7 @@ void Skeleton3DEditor::create_editors() { joint_tree = memnew(Tree); joint_tree->set_columns(1); - joint_tree->set_focus_mode(Control::FocusMode::FOCUS_NONE); + joint_tree->set_focus_mode(Control::FOCUS_NONE); joint_tree->set_select_mode(Tree::SELECT_SINGLE); joint_tree->set_hide_root(true); joint_tree->set_v_size_flags(SIZE_EXPAND_FILL); @@ -622,37 +610,30 @@ 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_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying()); - pose_editor->set_toggle_enabled(true); + 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); - - 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); } void Skeleton3DEditor::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { + edit_mode_button->set_icon(get_theme_icon("ToolBoneSelect", "EditorIcons")); + get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); + break; + } case NOTIFICATION_ENTER_TREE: { create_editors(); update_joint_tree(); update_editors(); - - get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); joint_tree->connect("item_rmb_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); #ifdef TOOLS_ENABLED + skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); -#endif // TOOLS_ENABLED - + 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; } } @@ -661,7 +642,8 @@ void Skeleton3DEditor::_notification(int p_what) { void Skeleton3DEditor::_node_removed(Node *p_node) { if (skeleton && p_node == skeleton) { skeleton = nullptr; - options->hide(); + skeleton_options->hide(); + rest_options->hide(); } _update_properties(); @@ -672,23 +654,236 @@ void Skeleton3DEditor::_bind_methods() { 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_properties"), &Skeleton3DEditor::_update_properties); - ClassDB::bind_method(D_METHOD("_on_click_option"), &Skeleton3DEditor::_on_click_option); + 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); + + ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &Skeleton3DEditor::drop_data_fw); - ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Skeleton3DEditor::drop_data_fw); ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone); + + ClassDB::bind_method(D_METHOD("_draw_gizmo"), &Skeleton3DEditor::_draw_gizmo); +} + +void Skeleton3DEditor::edit_mode_toggled(const bool pressed) { + edit_mode = pressed; + _update_gizmo_visible(); } Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) : editor(p_editor), editor_plugin(e_plugin), skeleton(p_skeleton) { + singleton = this; + + // Handle. + handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial)); + handle_shader = Ref<Shader>(memnew(Shader)); + handle_shader->set_code(R"( +// Skeleton 3D gizmo handle shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled, depth_draw_always; +uniform sampler2D texture_albedo : hint_albedo; +uniform float point_size : hint_range(0,128) = 32; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION.z = mix(POSITION.z, 0, 0.999); + POINT_SIZE = point_size; +} +void fragment() { + vec4 albedo_tex = texture(texture_albedo,POINT_COORD); + vec3 col = albedo_tex.rgb + COLOR.rgb; + col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0)); + ALBEDO = col; + if (albedo_tex.a < 0.5) { discard; } + ALPHA = albedo_tex.a; +} +)"); + handle_material->set_shader(handle_shader); + Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon("EditorBoneHandle", "EditorIcons"); + handle_material->set_shader_param("point_size", handle->get_width()); + handle_material->set_shader_param("texture_albedo", handle); + + handles_mesh_instance = memnew(MeshInstance3D); + handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); + handles_mesh.instantiate(); + handles_mesh_instance->set_mesh(handles_mesh); +} + +void Skeleton3DEditor::update_bone_original() { + if (!skeleton) { + return; + } + if (skeleton->get_bone_count() == 0 || selected_bone == -1) { + return; + } + 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() { + handles_mesh_instance->hide(); +} + +void Skeleton3DEditor::_draw_gizmo() { + if (!skeleton) { + return; + } + + // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode, + // the skeleton update will be done first and + // the drawing surface will be interrupted once and an error will occur. + skeleton->force_update_all_dirty_bones(); + + // Handles. + if (edit_mode) { + _draw_handles(); + } else { + _hide_handles(); + } +} + +void Skeleton3DEditor::_draw_handles() { + handles_mesh_instance->show(); + + const int bone_len = skeleton->get_bone_count(); + handles_mesh->clear_surfaces(); + handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); + + for (int i = 0; i < bone_len; i++) { + Color c; + if (i == selected_bone) { + c = Color(1, 1, 0); + } else { + c = Color(0.1, 0.25, 0.8); + } + Vector3 point = skeleton->get_bone_global_pose(i).origin; + handles_mesh->surface_set_color(c); + handles_mesh->surface_add_vertex(point); + } + handles_mesh->surface_end(); + handles_mesh->surface_set_material(0, handle_material); +} + +TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) { + if (!p_node) { + return nullptr; + } + + NodePath np = p_node->get_metadata(0); + if (np == p_path) { + return p_node; + } + + TreeItem *children = p_node->get_first_child(); + while (children) { + TreeItem *n = _find(children, p_path); + if (n) { + return n; + } + children = children->get_next(); + } + + return nullptr; +} + +void Skeleton3DEditor::_subgizmo_selection_change() { + if (!skeleton) { + return; + } + + // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree. + if (!edit_mode) { + skeleton->clear_subgizmo_selection(); + return; + } + + int selected = -1; + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + selected = se->get_selected_bone(); + } + + if (selected >= 0) { + Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos(); + for (int i = 0; i < gizmos.size(); i++) { + Ref<EditorNode3DGizmo> gizmo = gizmos[i]; + if (!gizmo.is_valid()) { + continue; + } + Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin(); + if (!plugin.is_valid()) { + continue; + } + skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected)); + break; + } + } else { + skeleton->clear_subgizmo_selection(); + } +} + +void Skeleton3DEditor::select_bone(int p_idx) { + if (p_idx >= 0) { + TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx)); + if (ti) { + // Make visible when it's collapsed. + TreeItem *node = ti->get_parent(); + while (node && node != joint_tree->get_root()) { + node->set_collapsed(false); + node = node->get_parent(); + } + ti->select(0); + joint_tree->scroll_to_item(ti); + } + } else { + selected_bone = -1; + joint_tree->deselect_all(); + _joint_tree_selection_changed(); + } } Skeleton3DEditor::~Skeleton3DEditor() { - if (options) { - Node3DEditor::get_singleton()->remove_control_from_menu_panel(options); + if (skeleton) { +#ifdef TOOLS_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); +#endif + handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance); + } + + handles_mesh_instance->queue_delete(); + + Node3DEditor *ne = Node3DEditor::get_singleton(); + + if (separator) { + ne->remove_control_from_menu_panel(separator); + memdelete(separator); + } + + if (skeleton_options) { + ne->remove_control_from_menu_panel(skeleton_options); + memdelete(skeleton_options); + } + + if (rest_options) { + ne->remove_control_from_menu_panel(rest_options); + memdelete(rest_options); + } + + if (edit_mode_button) { + ne->remove_control_from_menu_panel(edit_mode_button); + memdelete(edit_mode_button); } } @@ -700,16 +895,432 @@ void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object); ERR_FAIL_COND(!skeleton); - Skeleton3DEditor *skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton)); + skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton)); add_custom_control(skel_editor); } Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { editor = p_node; - Ref<EditorInspectorPluginSkeleton> skeleton_plugin; - skeleton_plugin.instantiate(); + skeleton_plugin = memnew(EditorInspectorPluginSkeleton); skeleton_plugin->editor = editor; EditorInspector::add_inspector_plugin(skeleton_plugin); + + Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin)); + Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); +} + +EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); + if (se->is_edit_mode()) { + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { + if (!ne->is_gizmo_visible()) { + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + if (mb->is_pressed()) { + se->update_bone_original(); + } + } + return EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } + return EditorPlugin::AFTER_GUI_INPUT_PASS; +} + +bool Skeleton3DEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("Skeleton3D"); +} + +void Skeleton3DEditor::_bone_enabled_changed(const int p_bone_id) { + _update_gizmo_visible(); +} + +void Skeleton3DEditor::_update_gizmo_visible() { + _subgizmo_selection_change(); + if (edit_mode) { + if (selected_bone == -1) { +#ifdef TOOLS_ENABLED + skeleton->set_transform_gizmo_visible(false); +#endif + } else { +#ifdef TOOLS_ENABLED + if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) { + skeleton->set_transform_gizmo_visible(true); + } else { + skeleton->set_transform_gizmo_visible(false); + } +#endif + } + } else { +#ifdef TOOLS_ENABLED + skeleton->set_transform_gizmo_visible(true); +#endif + } + _draw_gizmo(); +} + +int Skeleton3DEditor::get_selected_bone() const { + return selected_bone; +} + +Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { + unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + + selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); + selected_sh = Ref<Shader>(memnew(Shader)); + selected_sh->set_code(R"( +// Skeleton 3D gizmo bones shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION.z = mix(POSITION.z, 0, 0.998); +} +void fragment() { + ALBEDO = COLOR.rgb; + ALPHA = COLOR.a; +} +)"); + selected_mat->set_shader(selected_sh); + + // Regist properties in editor settings. + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0)); + EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1); + EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape", 1); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron")); +} + +bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; +} + +String Skeleton3DGizmoPlugin::get_gizmo_name() const { + return "Skeleton3D"; +} + +int Skeleton3DGizmoPlugin::get_priority() const { + return -1; +} + +int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND_V(!skeleton, -1); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + + if (!se->is_edit_mode()) { + return -1; + } + + if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { + return -1; + } + + // Select bone. + real_t grab_threshold = 4 * EDSCALE; + Vector3 ray_from = p_camera->get_global_transform().origin; + Transform3D gt = skeleton->get_global_transform(); + int closest_idx = -1; + real_t closest_dist = 1e10; + const int bone_len = skeleton->get_bone_count(); + for (int i = 0; i < bone_len; i++) { + Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin); + Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d); + real_t dist_3d = ray_from.distance_to(joint_pos_3d); + real_t dist_2d = p_point.distance_to(joint_pos_2d); + if (dist_2d < grab_threshold && dist_3d < closest_dist) { + closest_dist = dist_3d; + closest_idx = i; + } + } + + if (closest_idx >= 0) { + WARN_PRINT("ray:"); + WARN_PRINT(itos(closest_idx)); + se->select_bone(closest_idx); + return closest_idx; + } + + se->select_bone(-1); + return -1; +} + +Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND_V(!skeleton, Transform3D()); + + return skeleton->get_bone_global_pose(p_id); +} + +void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND(!skeleton); + + // Prepare for global to local. + Transform3D original_to_local = Transform3D(); + int parent_idx = skeleton->get_bone_parent(p_id); + if (parent_idx >= 0) { + original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); + } + Basis to_local = original_to_local.get_basis().inverse(); + + // Prepare transform. + Transform3D t = Transform3D(); + + // Basis. + t.basis = to_local * p_transform.get_basis(); + + // Origin. + Vector3 orig = Vector3(); + orig = skeleton->get_bone_pose(p_id).origin; + Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin; + t.origin = orig + to_local.xform(sub); + + // Apply transform. + 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) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND(!skeleton); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + 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(); +} + +void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + p_gizmo->clear(); + + int selected = -1; + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + selected = se->get_selected_bone(); + } + + Color bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/skeleton"); + Color selected_bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/selected_bone"); + real_t bone_axis_length = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_axis_length"); + int bone_shape = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_shape"); + + LocalVector<Color> axis_colors; + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); + + Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); + surface_tool->begin(Mesh::PRIMITIVE_LINES); + + if (p_gizmo->is_selected()) { + surface_tool->set_material(selected_mat); + } else { + unselected_mat->set_albedo(bone_color); + surface_tool->set_material(unselected_mat); + } + + Vector<Transform3D> grests; + grests.resize(skeleton->get_bone_count()); + + LocalVector<int> bones; + LocalVector<float> weights; + bones.resize(4); + weights.resize(4); + for (int i = 0; i < 4; i++) { + bones[i] = 0; + weights[i] = 0; + } + weights[0] = 1; + + int current_bone_index = 0; + Vector<int> bones_to_process = skeleton->get_parentless_bones(); + + while (bones_to_process.size() > current_bone_index) { + int current_bone_idx = bones_to_process[current_bone_index]; + current_bone_index++; + + Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color; + + Vector<int> child_bones_vector; + child_bones_vector = skeleton->get_bone_children(current_bone_idx); + int child_bones_size = child_bones_vector.size(); + + // You have children but no parent, then you must be a root/parentless bone. + if (skeleton->get_bone_parent(current_bone_idx) < 0) { + grests.write[current_bone_idx] = skeleton->get_bone_rest(current_bone_idx); + } + + for (int i = 0; i < child_bones_size; i++) { + // Something wrong. + if (child_bones_vector[i] < 0) { + continue; + } + + int child_bone_idx = child_bones_vector[i]; + + grests.write[child_bone_idx] = grests[current_bone_idx] * skeleton->get_bone_rest(child_bone_idx); + + Vector3 v0 = grests[current_bone_idx].origin; + Vector3 v1 = grests[child_bone_idx].origin; + Vector3 d = (v1 - v0).normalized(); + real_t dist = v0.distance_to(v1); + + // Find closest axis. + int closest = -1; + real_t closest_d = 0.0; + for (int j = 0; j < 3; j++) { + real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d)); + if (j == 0 || dp > closest_d) { + closest = j; + } + } + + // Draw bone. + switch (bone_shape) { + case 0: { // Wire shape. + surface_tool->set_color(current_bone_color); + bones[0] = current_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + bones[0] = child_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + } break; + + case 1: { // Octahedron shape. + Vector3 first; + Vector3 points[6]; + int point_idx = 0; + for (int j = 0; j < 3; j++) { + Vector3 axis; + if (first == Vector3()) { + axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized(); + first = axis; + } else { + axis = d.cross(first).normalized(); + } + + surface_tool->set_color(current_bone_color); + for (int k = 0; k < 2; k++) { + if (k == 1) { + axis = -axis; + } + Vector3 point = v0 + d * dist * 0.2; + point += axis * dist * 0.1; + + bones[0] = current_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(point); + + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(point); + bones[0] = child_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + points[point_idx++] = point; + } + } + surface_tool->set_color(current_bone_color); + SWAP(points[1], points[2]); + bones[0] = current_bone_idx; + for (int j = 0; j < 6; j++) { + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(points[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(points[(j + 1) % 6]); + } + } break; + } + + // Axis as root of the bone. + for (int j = 0; j < 3; j++) { + bones[0] = current_bone_idx; + surface_tool->set_color(axis_colors[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0 + (grests[current_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length); + + if (j == closest) { + continue; + } + } + + // Axis at the end of the bone children. + if (i == child_bones_size - 1) { + for (int j = 0; j < 3; j++) { + bones[0] = child_bone_idx; + surface_tool->set_color(axis_colors[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1 + (grests[child_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length); + + if (j == closest) { + continue; + } + } + } + + // Add the bone's children to the list of bones to be processed. + bones_to_process.push_back(child_bones_vector[i]); + } + } + + Ref<ArrayMesh> m = surface_tool->commit(); + 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 9de52c6fa8..3b4dd362fb 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -33,7 +33,12 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "editor/editor_properties.h" +#include "node_3d_editor_plugin.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" #include "scene/3d/skeleton_3d.h" +#include "scene/resources/immediate_mesh.h" class EditorInspectorPluginSkeleton; class Joint; @@ -41,29 +46,28 @@ class PhysicalBone3D; class Skeleton3DEditorPlugin; class Button; class CheckBox; -class EditorPropertyTransform3D; -class EditorPropertyVector3; class BoneTransformEditor : public VBoxContainer { GDCLASS(BoneTransformEditor, 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; @@ -73,18 +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); - // 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); @@ -92,28 +85,11 @@ protected: public: BoneTransformEditor(Skeleton3D *p_skeleton); - // Which transform target to modify + // Which transform target to modify. void set_target(const String &p_prop); void set_label(const String &p_label) { label = p_label; } void _update_properties(); - void _update_custom_pose_properties(); - void _update_transform_properties(Transform3D p_transform); - - // Can/cannot modify the spinner values for the Transform - void set_read_only(const bool p_read_only); - - // Transform can be keyed, whether or not to show the button - void set_keyable(const bool p_keyable); - - // Bone can be toggled enabled or disabled, whether or not to show the checkbox - void set_toggle_enabled(const bool p_enabled); - - // Key Transform Button pressed - void _key_button_pressed(); - - // Bone Enabled Checkbox toggled - void _checkbox_toggled(const bool p_toggled); }; class Skeleton3DEditor : public VBoxContainer { @@ -121,13 +97,20 @@ class Skeleton3DEditor : public VBoxContainer { friend class Skeleton3DEditorPlugin; - enum Menu { - MENU_OPTION_CREATE_PHYSICAL_SKELETON + enum SkeletonOption { + SKELETON_OPTION_INIT_POSE, + SKELETON_OPTION_INSERT_KEYS, + SKELETON_OPTION_INSERT_KEYS_EXISTED, + SKELETON_OPTION_CREATE_PHYSICAL_SKELETON + }; + + enum RestOption { + REST_OPTION_POSE_TO_REST }; struct BoneInfo { PhysicalBone3D *physical_bone = nullptr; - Transform3D relative_rest; // Relative to skeleton node + Transform3D relative_rest; // Relative to skeleton node. }; EditorNode *editor; @@ -138,15 +121,25 @@ class Skeleton3DEditor : public VBoxContainer { Tree *joint_tree = nullptr; BoneTransformEditor *rest_editor = nullptr; BoneTransformEditor *pose_editor = nullptr; - BoneTransformEditor *custom_pose_editor = nullptr; - MenuButton *options = nullptr; + VSeparator *separator; + MenuButton *skeleton_options = nullptr; + MenuButton *rest_options = nullptr; + Button *edit_mode_button; + + bool edit_mode = false; + EditorFileDialog *file_dialog = nullptr; - UndoRedo *undo_redo = nullptr; + bool keyable; - void _on_click_option(int p_option); + static Skeleton3DEditor *singleton; + + void _on_click_skeleton_option(int p_skeleton_option); + void _on_click_rest_option(int p_rest_option); void _file_selected(const String &p_file); + TreeItem *_find(TreeItem *p_node, const NodePath &p_path); + void edit_mode_toggled(const bool pressed); EditorFileDialog *file_export_lib = nullptr; @@ -155,6 +148,10 @@ class Skeleton3DEditor : public VBoxContainer { void create_editors(); + void init_pose(); + void insert_keys(bool p_all_bones); + void pose_to_rest(); + void create_physical_skeleton(); PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos); @@ -162,20 +159,57 @@ class Skeleton3DEditor : public VBoxContainer { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + void set_keyable(const bool p_keyable); + void set_rest_options_enabled(const bool p_rest_options_enabled); + + // Handle. + MeshInstance3D *handles_mesh_instance; + Ref<ImmediateMesh> handles_mesh; + Ref<ShaderMaterial> handle_material; + Ref<Shader> handle_shader; + + Vector3 bone_original_position; + Quaternion bone_original_rotation; + Vector3 bone_original_scale; + + void _update_gizmo_visible(); + void _bone_enabled_changed(const int p_bone_id); + + void _hide_handles(); + + void _draw_gizmo(); + void _draw_handles(); + + void _joint_tree_selection_changed(); + void _joint_tree_rmb_select(const Vector2 &p_pos); + void _update_properties(); + + void _subgizmo_selection_change(); + + int selected_bone = -1; + protected: void _notification(int p_what); void _node_removed(Node *p_node); static void _bind_methods(); public: + static Skeleton3DEditor *get_singleton() { return singleton; } + + void select_bone(int p_idx); + + int get_selected_bone() const; + void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx); Skeleton3D *get_skeleton() const { return skeleton; }; - void _joint_tree_selection_changed(); - void _joint_tree_rmb_select(const Vector2 &p_pos); + bool is_edit_mode() const { return edit_mode; } - void _update_properties(); + void update_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(); @@ -186,6 +220,7 @@ class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { friend class Skeleton3DEditorPlugin; + Skeleton3DEditor *skel_editor; EditorNode *editor; public: @@ -196,12 +231,40 @@ public: class Skeleton3DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton3DEditorPlugin, EditorPlugin); + EditorInspectorPluginSkeleton *skeleton_plugin; EditorNode *editor; public: - Skeleton3DEditorPlugin(EditorNode *p_node); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + + bool has_main_screen() const override { return false; } + virtual bool handles(Object *p_object) const override; virtual String get_name() const override { return "Skeleton3D"; } + + Skeleton3DEditorPlugin(EditorNode *p_node); +}; + +class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); + + Ref<StandardMaterial3D> unselected_mat; + Ref<ShaderMaterial> selected_mat; + Ref<Shader> selected_sh; + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + + int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override; + Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override; + void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override; + void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) override; + + void redraw(EditorNode3DGizmo *p_gizmo) override; + + Skeleton3DGizmoPlugin(); }; #endif // SKELETON_3D_EDITOR_PLUGIN_H 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/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 44db06bcfd..b9ec6bf5ab 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -58,6 +58,21 @@ void TexturePreview::_notification(int p_what) { } } +void TexturePreview::_update_metadata_label_text() { + Ref<Texture2D> texture = texture_display->get_texture(); + + String format; + if (Object::cast_to<ImageTexture>(*texture)) { + format = Image::get_format_name(Object::cast_to<ImageTexture>(*texture)->get_format()); + } else if (Object::cast_to<StreamTexture2D>(*texture)) { + format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*texture)->get_format()); + } else { + format = texture->get_class(); + } + + metadata_label->set_text(itos(texture->get_width()) + "x" + itos(texture->get_height()) + " " + format); +} + TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { checkerboard = memnew(TextureRect); checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); @@ -75,16 +90,8 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { if (p_show_metadata) { metadata_label = memnew(Label); - String format; - if (Object::cast_to<ImageTexture>(*p_texture)) { - format = Image::get_format_name(Object::cast_to<ImageTexture>(*p_texture)->get_format()); - } else if (Object::cast_to<StreamTexture2D>(*p_texture)) { - format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*p_texture)->get_format()); - } else { - format = p_texture->get_class(); - } - - metadata_label->set_text(itos(p_texture->get_width()) + "x" + itos(p_texture->get_height()) + " " + format); + _update_metadata_label_text(); + p_texture->connect("changed", callable_mp(this, &TexturePreview::_update_metadata_label_text)); // It's okay that these colors are static since the grid color is static too. metadata_label->add_theme_color_override("font_color", Color::named("white")); diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index 36a5513ea6..60349febd7 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -44,6 +44,8 @@ private: TextureRect *checkerboard = nullptr; Label *metadata_label = nullptr; + void _update_metadata_label_text(); + protected: void _notification(int p_what); diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index cfb2d63bc7..ce90d61616 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -50,7 +50,7 @@ void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) { EditorNode::get_singleton()->get_theme_base()->get_theme_color(SNAME("mono_color"), SNAME("Editor")).inverted() * Color(1, 1, 1, 0.5), Math::round(2 * EDSCALE)); - while ((to - from).length_squared() > 200) { + while (from.distance_squared_to(to) > 200) { edit_draw->draw_line( from, from + line, @@ -674,8 +674,7 @@ void TextureRegionEditor::_zoom_on_position(float p_zoom, Point2 p_position) { draw_zoom = p_zoom; Point2 ofs = p_position; ofs = ofs / prev_zoom - ofs / draw_zoom; - draw_ofs.x = Math::round(draw_ofs.x + ofs.x); - draw_ofs.y = Math::round(draw_ofs.y + ofs.y); + draw_ofs = (draw_ofs + ofs).round(); edit_draw->update(); } diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 165a381407..19e1b40a0d 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -437,8 +437,8 @@ void ThemeItemImportTree::_update_total_selected(Theme::DataType p_data_type) { } int count = 0; - for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) { - ThemeItem ti = E->key(); + for (const KeyValue<ThemeItem, ItemCheckedState> &E : selected_items) { + ThemeItem ti = E.key; if (ti.data_type == p_data_type) { count++; } @@ -759,7 +759,7 @@ void ThemeItemImportTree::_import_selected() { ProgressDialog::get_singleton()->add_task("import_theme_items", TTR("Importing Theme Items"), selected_items.size() + 2); int idx = 0; - for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) { + for (KeyValue<ThemeItem, ItemCheckedState> &E : selected_items) { // Arbitrary number of items to skip from reporting. // Reduces the number of UI updates that this causes when copying large themes. if (idx % 10 == 0) { @@ -769,8 +769,8 @@ void ThemeItemImportTree::_import_selected() { ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Importing items {n}/{n}").format(arr, "{n}"), idx); } - ItemCheckedState cs = E->get(); - ThemeItem ti = E->key(); + ItemCheckedState cs = E.value; + ThemeItem ti = E.key; if (cs == SELECT_IMPORT_DEFINITION || cs == SELECT_IMPORT_FULL) { Variant item_value = Variant(); @@ -3333,7 +3333,7 @@ ThemeEditor::ThemeEditor() { preview_tabs->set_h_size_flags(SIZE_EXPAND_FILL); preview_tabbar_hb->add_child(preview_tabs); preview_tabs->connect("tab_changed", callable_mp(this, &ThemeEditor::_change_preview_tab)); - preview_tabs->connect("right_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab)); + preview_tabs->connect("tab_rmb_clicked", callable_mp(this, &ThemeEditor::_remove_preview_tab)); HBoxContainer *add_preview_button_hb = memnew(HBoxContainer); preview_tabbar_hb->add_child(add_preview_button_hb); diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index 801ee0eac2..d26d0ec39d 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -126,8 +126,8 @@ void ThemeEditorPreview::_draw_picker_overlay() { highlight_label_rect.size.x += margin_left + margin_right; highlight_label_rect.size.y += margin_top + margin_bottom; - highlight_label_rect.position.x = CLAMP(highlight_label_rect.position.x, 0.0, picker_overlay->get_size().width); - highlight_label_rect.position.y = CLAMP(highlight_label_rect.position.y, 0.0, picker_overlay->get_size().height); + highlight_label_rect.position = highlight_label_rect.position.clamp(Vector2(), picker_overlay->get_size()); + picker_overlay->draw_style_box(theme_cache.preview_picker_label, highlight_label_rect); Point2 label_pos = highlight_label_rect.position; diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index e98bd74b62..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(); @@ -522,10 +519,10 @@ void TileAtlasView::_update_alternative_tiles_rect_cache() { } Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const { - for (Map<Vector2, Map<int, Rect2i>>::Element *E_coords = alternative_tiles_rect_cache.front(); E_coords; E_coords = E_coords->next()) { - for (Map<int, Rect2i>::Element *E_alternative = E_coords->value().front(); E_alternative; E_alternative = E_alternative->next()) { - if (E_alternative->value().has_point(p_pos)) { - return Vector3i(E_coords->key().x, E_coords->key().y, E_alternative->key()); + for (const KeyValue<Vector2, Map<int, Rect2i>> &E_coords : alternative_tiles_rect_cache) { + for (const KeyValue<int, Rect2i> &E_alternative : E_coords.value) { + if (E_alternative.value.has_point(p_pos)) { + return Vector3i(E_coords.key.x, E_coords.key.y, E_alternative.key); } } } @@ -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 ea7ca787c8..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) { @@ -231,10 +241,14 @@ void GenericTilePolygonEditor::_zoom_changed() { void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { switch (p_item_pressed) { - case RESET_TO_DEFAULT_TILE: + case RESET_TO_DEFAULT_TILE: { undo_redo->create_action(TTR("Edit Polygons")); undo_redo->add_do_method(this, "clear_polygons"); - undo_redo->add_do_method(this, "add_polygon", tile_set->get_tile_shape_polygon()); + Vector<Vector2> polygon = tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * tile_set->get_tile_size(); + } + undo_redo->add_do_method(this, "add_polygon", polygon); undo_redo->add_do_method(base_control, "update"); undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); undo_redo->add_undo_method(this, "clear_polygons"); @@ -244,8 +258,8 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { undo_redo->add_undo_method(base_control, "update"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); - break; - case CLEAR_TILE: + } break; + case CLEAR_TILE: { undo_redo->create_action(TTR("Edit Polygons")); undo_redo->add_do_method(this, "clear_polygons"); undo_redo->add_do_method(base_control, "update"); @@ -257,7 +271,7 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { undo_redo->add_undo_method(base_control, "update"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); - break; + } break; default: break; } @@ -308,6 +322,9 @@ void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_cur ERR_FAIL_COND(!tile_set.is_valid()); Vector<Point2> polygon = tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * tile_set->get_tile_size(); + } Point2 snapped_point = r_point; // Snap to polygon vertices. @@ -539,7 +556,11 @@ void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) { // Set the default tile shape clear_polygons(); if (p_tile_set.is_valid()) { - add_polygon(p_tile_set->get_tile_shape_polygon()); + Vector<Vector2> polygon = p_tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * p_tile_set->get_tile_size(); + } + add_polygon(polygon); } } tile_set = p_tile_set; @@ -622,7 +643,7 @@ void GenericTilePolygonEditor::_notification(int p_what) { button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenu"), SNAME("EditorIcons"))); + button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); break; } } @@ -745,10 +766,10 @@ Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_s } void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), E->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), p_new_value); + for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E.key.alternative_tile, property), E.value); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E.key.alternative_tile, property), p_new_value); } } @@ -1180,10 +1201,10 @@ Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_ } void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), E->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), p_new_value); + for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, occlusion_layer), E.value); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, occlusion_layer), p_new_value); } } @@ -1265,17 +1286,21 @@ void TileDataCollisionEditor::_polygons_changed() { } Variant TileDataCollisionEditor::_get_painted_value() { + Dictionary dict; + dict["linear_velocity"] = dummy_object->get("linear_velocity"); + dict["angular_velocity"] = dummy_object->get("angular_velocity"); Array array; for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { ERR_FAIL_COND_V(polygon_editor->get_polygon(i).size() < 3, Variant()); - Dictionary dict; - dict["points"] = polygon_editor->get_polygon(i); - dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); - dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); - array.push_back(dict); + Dictionary polygon_dict; + polygon_dict["points"] = polygon_editor->get_polygon(i); + polygon_dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); + polygon_dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); + array.push_back(polygon_dict); } + dict["polygons"] = array; - return array; + return dict; } void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { @@ -1291,12 +1316,14 @@ void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_ } _polygons_changed(); + dummy_object->set("linear_velocity", tile_data->get_constant_linear_velocity(physics_layer)); + dummy_object->set("angular_velocity", tile_data->get_constant_angular_velocity(physics_layer)); for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { dummy_object->set(vformat("polygon_%d_one_way", i), tile_data->is_collision_polygon_one_way(physics_layer, i)); dummy_object->set(vformat("polygon_%d_one_way_margin", i), tile_data->get_collision_polygon_one_way_margin(physics_layer, i)); } - for (Map<StringName, EditorProperty *>::Element *E = property_editors.front(); E; E = E->next()) { - E->get()->update_property(); + for (const KeyValue<StringName, EditorProperty *> &E : property_editors) { + E.value->update_property(); } polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); @@ -1306,13 +1333,16 @@ void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_so TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); ERR_FAIL_COND(!tile_data); - Array array = p_value; + Dictionary dict = p_value; + tile_data->set_constant_linear_velocity(physics_layer, dict["linear_velocity"]); + tile_data->set_constant_angular_velocity(physics_layer, dict["angular_velocity"]); + Array array = dict["polygons"]; tile_data->set_collision_polygons_count(physics_layer, array.size()); for (int i = 0; i < array.size(); i++) { - Dictionary dict = array[i]; - tile_data->set_collision_polygon_points(physics_layer, i, dict["points"]); - tile_data->set_collision_polygon_one_way(physics_layer, i, dict["one_way"]); - tile_data->set_collision_polygon_one_way_margin(physics_layer, i, dict["one_way_margin"]); + Dictionary polygon_dict = array[i]; + tile_data->set_collision_polygon_points(physics_layer, i, polygon_dict["points"]); + tile_data->set_collision_polygon_one_way(physics_layer, i, polygon_dict["one_way"]); + tile_data->set_collision_polygon_one_way_margin(physics_layer, i, polygon_dict["one_way_margin"]); } polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); @@ -1322,37 +1352,41 @@ Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); ERR_FAIL_COND_V(!tile_data, Variant()); + Dictionary dict; + dict["linear_velocity"] = tile_data->get_constant_linear_velocity(physics_layer); + dict["angular_velocity"] = tile_data->get_constant_angular_velocity(physics_layer); Array array; for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { - Dictionary dict; - dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); - dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); - dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); - array.push_back(dict); + Dictionary polygon_dict; + polygon_dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); + polygon_dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); + polygon_dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); + array.push_back(polygon_dict); } - return array; + dict["polygons"] = array; + return dict; } void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { Array new_array = p_new_value; - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Array old_array = E->get(); + for (KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Array old_array = E.value; - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), old_array.size()); + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), old_array.size()); for (int i = 0; i < old_array.size(); i++) { Dictionary dict = old_array[i]; - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["points"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way_margin"]); } - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), new_array.size()); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), new_array.size()); for (int i = 0; i < new_array.size(); i++) { Dictionary dict = new_array[i]; - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["points"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way_margin"]); } } } @@ -1378,6 +1412,27 @@ TileDataCollisionEditor::TileDataCollisionEditor() { polygon_editor->connect("polygons_changed", callable_mp(this, &TileDataCollisionEditor::_polygons_changed)); add_child(polygon_editor); + dummy_object->add_dummy_property("linear_velocity"); + dummy_object->set("linear_velocity", Vector2()); + dummy_object->add_dummy_property("angular_velocity"); + dummy_object->set("angular_velocity", 0.0); + + EditorProperty *linear_velocity_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::VECTOR2, "linear_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + linear_velocity_editor->set_object_and_property(dummy_object, "linear_velocity"); + linear_velocity_editor->set_label("linear_velocity"); + linear_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + linear_velocity_editor->update_property(); + add_child(linear_velocity_editor); + property_editors["linear_velocity"] = linear_velocity_editor; + + EditorProperty *angular_velocity_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::FLOAT, "angular_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + angular_velocity_editor->set_object_and_property(dummy_object, "angular_velocity"); + angular_velocity_editor->set_label("angular_velocity"); + angular_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + angular_velocity_editor->update_property(); + add_child(angular_velocity_editor); + property_editors["angular_velocity"] = linear_velocity_editor; + _polygons_changed(); } @@ -1976,16 +2031,16 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t drag_type = DRAG_TYPE_NONE; } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Terrain Set")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), drag_painted_value); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), dict["terrain_set"]); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } } @@ -1996,17 +2051,17 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t int terrain_set = int(painted["terrain_set"]); int terrain = int(painted["terrain"]); undo_redo->create_action(TTR("Painting Terrain")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), terrain); } if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } } @@ -2259,14 +2314,14 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi } else { if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Tiles Property")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), dict["terrain_set"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), drag_painted_value); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } undo_redo->commit_action(false); @@ -2276,17 +2331,17 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi int terrain_set = int(painted["terrain_set"]); int terrain = int(painted["terrain"]); undo_redo->create_action(TTR("Painting Terrain")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), terrain); } if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } } @@ -2398,10 +2453,10 @@ Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atla } void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), E->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), p_new_value); + for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, navigation_layer), E.value); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, navigation_layer), p_new_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 8b5b576369..5ca39b82b2 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -455,34 +455,36 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p switch (drag_type) { case DRAG_TYPE_PAINT: { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + 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)); for (int i = 0; i < line.size(); i++) { if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + 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; 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,31 +528,32 @@ 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 (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + 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(); @@ -557,18 +565,19 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p for (int i = 0; i < line.size(); i++) { if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + 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(); } } } @@ -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,20 +710,20 @@ 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()); } // Expand the grid if needed if (expand_grid && !preview.is_empty()) { drawn_grid_rect = Rect2i(preview.front()->key(), Vector2i(1, 1)); - for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) { - drawn_grid_rect.expand_to(E->key()); + for (const KeyValue<Vector2i, TileMapCell> &E : preview) { + drawn_grid_rect.expand_to(E.key); } } } @@ -748,23 +760,23 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over } // Draw the preview. - for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) { + for (const KeyValue<Vector2i, TileMapCell> &E : preview) { Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(E->key())); + 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->get().source_id)) { - TileSetSource *source = *tile_set->get_source(E->get().source_id); + if (tile_set->has_source(E.value.source_id)) { + TileSetSource *source = *tile_set->get_source(E.value.source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { // Get tile data. - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E.value.get_atlas_coords(), E.value.alternative_tile)); // Compute the offset - Rect2i source_rect = atlas_source->get_tile_texture_region(E->get().get_atlas_coords()); - Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E->get().get_atlas_coords(), E->get().alternative_tile); + Rect2i source_rect = atlas_source->get_tile_texture_region(E.value.get_atlas_coords()); + Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E.value.get_atlas_coords(), E.value.alternative_tile); // Compute the destination rectangle in the CanvasItem. Rect2 dest_rect; @@ -772,9 +784,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over bool transpose = tile_data->get_transpose(); if (transpose) { - dest_rect.position = (tile_map->map_to_world(E->key()) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + dest_rect.position = (tile_map->map_to_world(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); } else { - dest_rect.position = (tile_map->map_to_world(E->key()) - dest_rect.size / 2 - tile_offset); + dest_rect.position = (tile_map->map_to_world(E.key) - dest_rect.size / 2 - tile_offset); } dest_rect = xform.xform(dest_rect); @@ -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 { @@ -1041,7 +1053,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i TypedArray<Vector2i> to_check; if (source.source_id == TileSet::INVALID_SOURCE) { Rect2i rect = tile_map->get_used_rect(); - if (rect.size.x <= 0 || rect.size.y <= 0) { + if (rect.has_no_area()) { rect = Rect2i(p_coords, Vector2i(1, 1)); } for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) { @@ -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 { @@ -1165,11 +1177,11 @@ void TileMapEditorTilesPlugin::_stop_dragging() { } undo_redo->create_action(TTR("Move tiles")); // Move the tiles. - for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : cells_do) { + 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); } - for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) { - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : cells_undo) { + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } // Update the selection. @@ -1205,41 +1217,41 @@ void TileMapEditorTilesPlugin::_stop_dragging() { } break; case DRAG_TYPE_PAINT: { undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } undo_redo->commit_action(false); } break; case DRAG_TYPE_LINE: { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos); undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + 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->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); + 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); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); } undo_redo->commit_action(); } break; case DRAG_TYPE_RECT: { 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 (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + 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->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); + 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); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); } undo_redo->commit_action(); } break; case DRAG_TYPE_BUCKET: { undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } undo_redo->commit_action(false); } break; @@ -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) { @@ -1364,17 +1399,17 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( } int vertical_offset = 0; - for (Map<int, List<const TileMapCell *>>::Element *E_source = per_source.front(); E_source; E_source = E_source->next()) { + for (const KeyValue<int, List<const TileMapCell *>> &E_source : per_source) { // Per source. List<const TileMapCell *> unorganized; Rect2i encompassing_rect_coords; Map<Vector2i, const TileMapCell *> organized_pattern; - TileSetSource *source = *tile_set->get_source(E_source->key()); + TileSetSource *source = *tile_set->get_source(E_source.key); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { // Organize using coordinates. - for (const TileMapCell *current : E_source->get()) { + for (const TileMapCell *current : E_source.value) { if (current->alternative_tile == 0) { organized_pattern[current->get_atlas_coords()] = current; } else { @@ -1393,14 +1428,14 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( } } else { // Add everything unorganized. - for (const TileMapCell *cell : E_source->get()) { + for (const TileMapCell *cell : E_source.value) { unorganized.push_back(cell); } } // Now add everything to the output pattern. - for (Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); E_cell; E_cell = E_cell->next()) { - selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + for (const KeyValue<Vector2i, const TileMapCell *> &E_cell : organized_pattern) { + selection_pattern->set_cell(E_cell.key - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell.value->source_id, E_cell.value->get_atlas_coords(), E_cell.value->alternative_tile); } Vector2i organized_size = selection_pattern->get_size(); int unorganized_index = 0; @@ -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); @@ -2308,14 +2348,14 @@ Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugi // Returns all tiles compatible with the given constraints. Set<TerrainsTilePattern> compatible_terrain_tile_patterns; - for (Map<TerrainsTilePattern, Set<TileMapCell>>::Element *E = per_terrain_terrains_tile_patterns_tiles[p_terrain_set].front(); E; E = E->next()) { + for (const KeyValue<TerrainsTilePattern, Set<TileMapCell>> &E : per_terrain_terrains_tile_patterns_tiles[p_terrain_set]) { int valid = true; int in_pattern_count = 0; for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) { // Check if the bit is compatible with the constraints. - Constraint terrain_bit_constraint = Constraint(tile_map, p_position, bit, E->key()[in_pattern_count]); + Constraint terrain_bit_constraint = Constraint(tile_map, p_position, bit, E.key[in_pattern_count]); Set<Constraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint); if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { @@ -2327,7 +2367,7 @@ Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugi } if (valid) { - compatible_terrain_tile_patterns.insert(E->key()); + compatible_terrain_tile_patterns.insert(E.key); } } @@ -2368,15 +2408,15 @@ Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_c // Count the number of occurrences per terrain. Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); - for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_overlapping = overlapping_terrain_bits.front(); E_overlapping; E_overlapping = E_overlapping->next()) { - if (!p_to_replace.has(E_overlapping->key())) { - TileMapCell neighbor_cell = tile_map->get_cell(tile_map_layer, E_overlapping->key()); + for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) { + if (!p_to_replace.has(E_overlapping.key)) { + TileMapCell neighbor_cell = tile_map->get_cell(tile_map_layer, E_overlapping.key); TileData *neighbor_tile_data = nullptr; if (terrain_tiles.has(neighbor_cell) && terrain_tiles[neighbor_cell]->get_terrain_set() == p_terrain_set) { neighbor_tile_data = terrain_tiles[neighbor_cell]; } - int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping->get())) : -1; + int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping.value)) : -1; if (terrain_count.has(terrain)) { terrain_count[terrain] = 0; } @@ -2387,10 +2427,10 @@ Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_c // Get the terrain with the max number of occurrences. int max = 0; int max_terrain = -1; - for (Map<int, int>::Element *E_terrain_count = terrain_count.front(); E_terrain_count; E_terrain_count = E_terrain_count->next()) { - if (E_terrain_count->get() > max) { - max = E_terrain_count->get(); - max_terrain = E_terrain_count->key(); + for (const KeyValue<int, int> &E_terrain_count : terrain_count) { + if (E_terrain_count.value > max) { + max = E_terrain_count.value; + max_terrain = E_terrain_count.key; } } @@ -2458,15 +2498,15 @@ Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTer while (!to_replace.is_empty()) { // Compute the minimum number of tile possibilities for each cell. int min_nb_possibilities = 100000000; - for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) { - min_nb_possibilities = MIN(min_nb_possibilities, E->get().size()); + for (const KeyValue<Vector2i, Set<TerrainsTilePattern>> &E : per_cell_acceptable_tiles) { + min_nb_possibilities = MIN(min_nb_possibilities, E.value.size()); } // Get the set of possible cells to fill. LocalVector<Vector2i> to_choose_from; - for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) { - if (E->get().size() == min_nb_possibilities) { - to_choose_from.push_back(E->key()); + for (const KeyValue<Vector2i, Set<TerrainsTilePattern>> &E : per_cell_acceptable_tiles) { + if (E.value.size() == min_nb_possibilities) { + to_choose_from.push_back(E.key); } } @@ -2584,9 +2624,9 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map // Add the constraints from the added tiles. Set<TileMapEditorTerrainsPlugin::Constraint> added_tiles_constraints_set; - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - Vector2i coords = E_to_paint->key(); - TerrainsTilePattern terrains_tile_pattern = E_to_paint->get(); + for (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) { + Vector2i coords = E_to_paint.key; + TerrainsTilePattern terrains_tile_pattern = E_to_paint.value; Set<TileMapEditorTerrainsPlugin::Constraint> cell_constraints = _get_constraints_from_added_tile(coords, p_terrain_set, terrains_tile_pattern); for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = cell_constraints.front(); E; E = E->next()) { @@ -2596,8 +2636,8 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map // Build the list of potential tiles to replace. Set<Vector2i> potential_to_replace; - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - Vector2i coords = E_to_paint->key(); + for (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) { + Vector2i coords = E_to_paint.key; for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { if (tile_map->is_existing_neighbor(TileSet::CellNeighbor(i))) { Vector2i neighbor = tile_map->get_neighbor_cell(coords, TileSet::CellNeighbor(i)); @@ -2612,8 +2652,8 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map Set<Vector2i> to_replace; // Add the central tiles to the one to replace. TODO: maybe change that. - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - to_replace.insert(E_to_paint->key()); + for (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) { + to_replace.insert(E_to_paint.key); } // Add the constraints from the surroundings of the modified areas. @@ -2627,9 +2667,9 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map Map<Constraint, Set<Vector2i>> source_tiles_of_constraint; for (Set<Constraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) { Map<Vector2i, TileSet::CellNeighbor> sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits(); - for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_source_tile_of_constraint = sources_of_constraint.front(); E_source_tile_of_constraint; E_source_tile_of_constraint = E_source_tile_of_constraint->next()) { - if (potential_to_replace.has(E_source_tile_of_constraint->key())) { - source_tiles_of_constraint[E->get()].insert(E_source_tile_of_constraint->key()); + for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_source_tile_of_constraint : sources_of_constraint) { + if (potential_to_replace.has(E_source_tile_of_constraint.key)) { + source_tiles_of_constraint[E->get()].insert(E_source_tile_of_constraint.key); } } } @@ -2646,8 +2686,8 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map potential_to_replace.erase(to_add_to_remove); to_replace.insert(to_add_to_remove); to_replace_modified = true; - for (Map<Constraint, Set<Vector2i>>::Element *E_source_tiles_of_constraint = source_tiles_of_constraint.front(); E_source_tiles_of_constraint; E_source_tiles_of_constraint = E_source_tiles_of_constraint->next()) { - E_source_tiles_of_constraint->get().erase(to_add_to_remove); + for (KeyValue<Constraint, Set<Vector2i>> &E_source_tiles_of_constraint : source_tiles_of_constraint) { + E_source_tiles_of_constraint.value.erase(to_add_to_remove); } break; } @@ -2665,13 +2705,13 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map Map<Vector2i, TerrainsTilePattern> wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints); // Use the WFC run for the output. - for (Map<Vector2i, TerrainsTilePattern>::Element *E = wfc_output.front(); E; E = E->next()) { - output[E->key()] = _get_random_tile_from_pattern(p_terrain_set, E->get()); + for (const KeyValue<Vector2i, TerrainsTilePattern> &E : wfc_output) { + output[E.key] = _get_random_tile_from_pattern(p_terrain_set, E.value); } // Override the WFC results to make sure at least the painted tiles are actually painted. - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - output[E_to_paint->key()] = _get_random_tile_from_pattern(p_terrain_set, E_to_paint->get()); + for (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) { + output[E_to_paint.key] = _get_random_tile_from_pattern(p_terrain_set, E_to_paint.value); } return output; @@ -2749,9 +2789,9 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { } break; case DRAG_TYPE_PAINT: { undo_redo->create_action(TTR("Paint terrain")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } undo_redo->commit_action(false); } break; @@ -2825,11 +2865,11 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> to_draw[line[i]] = selected_terrains_tile_pattern; } Map<Vector2i, TileMapCell> modified = _draw_terrains(to_draw, selected_terrain_set); - for (Map<Vector2i, TileMapCell>::Element *E = modified.front(); E; E = E->next()) { - if (!drag_modified.has(E->key())) { - drag_modified[E->key()] = tile_map->get_cell(tile_map_layer, E->key()); + for (const KeyValue<Vector2i, TileMapCell> &E : modified) { + if (!drag_modified.has(E.key)) { + drag_modified[E.key] = tile_map->get_cell(tile_map_layer, E.key); } - tile_map->set_cell(tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } break; @@ -2864,9 +2904,9 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_tile_pattern; Map<Vector2i, TileMapCell> to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - drag_modified[E->key()] = tile_map->get_cell(tile_map_layer, E->key()); - tile_map->set_cell(tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + drag_modified[E.key] = tile_map->get_cell(tile_map_layer, E.key); + tile_map->set_cell(tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } 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 54185d1d2a..c43a854d9a 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -87,7 +87,7 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get_property_list p_list->push_back(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "")); } void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() { @@ -131,6 +131,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na return false; } + // ID and size related properties. if (tiles.size() == 1) { const Vector2i &coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; @@ -160,36 +161,6 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i); emit_signal(SNAME("changed"), "size_in_atlas"); return true; - } else if (p_name == "animation_columns") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), p_value, tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_columns(coords, p_value); - emit_signal(SNAME("changed"), "animation_columns"); - return true; - } else if (p_name == "animation_separation") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), p_value, tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_separation(coords, p_value); - emit_signal(SNAME("changed"), "animation_separation"); - return true; - } else if (p_name == "animation_speed") { - tile_set_atlas_source->set_tile_animation_speed(coords, p_value); - emit_signal(SNAME("changed"), "animation_speed"); - return true; - } else if (p_name == "animation_frames_count") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), p_value, coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_frames_count(coords, p_value); - notify_property_list_changed(); - emit_signal(SNAME("changed"), "animation_separation"); - return true; - } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { - int frame = components[0].trim_prefix("animation_frame_").to_int(); - ERR_FAIL_INDEX_V(frame, tile_set_atlas_source->get_tile_animation_frames_count(coords), false); - if (components[1] == "duration") { - tile_set_atlas_source->set_tile_animation_frame_duration(coords, frame, p_value); - return true; - } } } else if (alternative > 0) { if (p_name == "alternative_id") { @@ -213,6 +184,74 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "animation_columns") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_separation(tile.tile), tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_columns(tile.tile, p_value); + } + } + emit_signal(SNAME("changed"), "animation_columns"); + return true; + } else if (p_name == "animation_separation") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_separation(tile.tile, p_value); + } + } + emit_signal(SNAME("changed"), "animation_separation"); + return true; + } else if (p_name == "animation_speed") { + for (TileSelection tile : tiles) { + tile_set_atlas_source->set_tile_animation_speed(tile.tile, p_value); + } + emit_signal(SNAME("changed"), "animation_speed"); + return true; + } else if (p_name == "animation_frames_count") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), tile_set_atlas_source->get_tile_animation_separation(tile.tile), p_value, tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_frames_count(tile.tile, p_value); + } + } + notify_property_list_changed(); + emit_signal(SNAME("changed"), "animation_separation"); + return true; + } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { + for (TileSelection tile : tiles) { + int frame = components[0].trim_prefix("animation_frame_").to_int(); + if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(tile.tile)) { + ERR_PRINT(vformat("No tile animation frame with index %d", frame)); + } else { + if (components[1] == "duration") { + tile_set_atlas_source->set_tile_animation_frame_duration(tile.tile, frame, p_value); + return true; + } + } + } + } + } + + // Other properties. bool any_valid = false; for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { const Vector2i &coords = E->get().tile; @@ -238,6 +277,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na return false; } + // ID and size related properties.s if (tiles.size() == 1) { const Vector2i &coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; @@ -250,27 +290,6 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na } else if (p_name == "size_in_atlas") { r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords); return true; - } else if (p_name == "animation_columns") { - r_ret = tile_set_atlas_source->get_tile_animation_columns(coords); - return true; - } else if (p_name == "animation_separation") { - r_ret = tile_set_atlas_source->get_tile_animation_separation(coords); - return true; - } else if (p_name == "animation_speed") { - r_ret = tile_set_atlas_source->get_tile_animation_speed(coords); - return true; - } else if (p_name == "animation_frames_count") { - r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords); - return true; - } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { - int frame = components[0].trim_prefix("animation_frame_").to_int(); - if (components[1] == "duration") { - if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) { - return false; - } - r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame); - return true; - } } } else if (alternative > 0) { if (p_name == "alternative_id") { @@ -280,6 +299,44 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + const Vector2i &coords = tiles.front()->get().tile; + + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "animation_columns") { + r_ret = tile_set_atlas_source->get_tile_animation_columns(coords); + return true; + } else if (p_name == "animation_separation") { + r_ret = tile_set_atlas_source->get_tile_animation_separation(coords); + return true; + } else if (p_name == "animation_speed") { + r_ret = tile_set_atlas_source->get_tile_animation_speed(coords); + return true; + } else if (p_name == "animation_frames_count") { + r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords); + return true; + } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[0].trim_prefix("animation_frame_").to_int(); + if (components[1] == "duration") { + if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) { + return false; + } + r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame); + return true; + } + } + } + for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { // Return the first tile with a property matching the name. // Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit. @@ -304,29 +361,42 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro return; } + // ID and size related properties. if (tiles.size() == 1) { if (tiles.front()->get().alternative == 0) { p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "")); - - // Animation. - p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP)); - p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); - if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); - } else { - for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) { - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "")); - } - } } else { p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, "")); } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); + // Not optimal, but returns value for the first tile. This is similar to what MultiNodeEdit does. + if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); + } else { + for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) { + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "")); + } + } + } + // Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit). struct PropertyId { int occurence_id = 0; @@ -450,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() { @@ -674,9 +741,9 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { #undef ADD_TILE_DATA_EDITOR // Add tile data editors as children. - for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) { + for (KeyValue<String, TileDataEditor *> &E : tile_data_editors) { // Tile Data Editor. - TileDataEditor *tile_data_editor = E->get(); + TileDataEditor *tile_data_editor = E.value; if (!tile_data_editor->is_inside_tree()) { tile_data_painting_editor_container->add_child(tile_data_editor); } @@ -693,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); } @@ -716,9 +787,9 @@ void TileSetAtlasSourceEditor::_update_current_tile_data_editor() { } // Hide all editors but the current one. - for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) { - E->get()->hide(); - E->get()->get_toolbar()->hide(); + for (const KeyValue<String, TileDataEditor *> &E : tile_data_editors) { + E.value->hide(); + E.value->get_toolbar()->hide(); } if (tile_data_editors.has(property)) { current_tile_data_editor = tile_data_editors[property]; @@ -889,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(); @@ -926,11 +997,11 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + Vector2 pos = rect.position + rect.size * coords[i]; if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; } @@ -991,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(); @@ -1031,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(); @@ -1136,7 +1207,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + Vector2 pos = rect.position + rect.size * coords[i]; if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP_LEFT + i * 2); drag_start_mouse_pos = mouse_local_pos; @@ -1145,7 +1216,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP + i * 2); drag_start_mouse_pos = mouse_local_pos; @@ -1530,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; @@ -1614,13 +1682,13 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + Vector2 pos = rect.position + rect.size * coords[i]; if (can_grow[i] && can_grow[(i + 3) % 4]) { tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false); } else { tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false); } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i]) { tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false); } else { @@ -1942,8 +2010,13 @@ 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_changed_needs_update = false; // Avoid updating too many things. + _update_atlas_view(); } void TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed(String p_what) { @@ -1960,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 } @@ -1998,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. @@ -2011,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. @@ -2153,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(); @@ -2166,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: @@ -2206,7 +2315,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { middle_vbox_container->add_child(tile_inspector_label); tile_proxy_object = memnew(AtlasTileProxyObject(this)); - tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1)); + tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_proxy_object_changed)); tile_inspector = memnew(EditorInspector); tile_inspector->set_undo_redo(undo_redo); @@ -2331,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)); @@ -2406,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 501416c340..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,8 @@ 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); void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index f51f4625a9..d0d01a8d49 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -246,11 +246,9 @@ void TilesEditorPlugin::make_visible(bool p_visible) { if (p_visible) { tiles_editor_button->show(); editor_node->make_bottom_panel_item_visible(tiles_editor); - //get_tree()->connect_compat("idle_frame", tileset_editor, "_on_workspace_process"); } else { editor_node->hide_bottom_panel(); tiles_editor_button->hide(); - //get_tree()->disconnect_compat("idle_frame", tileset_editor, "_on_workspace_process"); } } diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 27f30479cf..0a067dfc17 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -123,9 +123,9 @@ void VisualShaderGraphPlugin::set_connections(List<VisualShader::Connection> &p_ void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id) { if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].output_ports.has(p_port_id)) { - for (Map<int, Port>::Element *E = links[p_node_id].output_ports.front(); E; E = E->next()) { - if (E->value().preview_button != nullptr) { - E->value().preview_button->set_pressed(false); + for (const KeyValue<int, Port> &E : links[p_node_id].output_ports) { + if (E.value.preview_button != nullptr) { + E.value.preview_button->set_pressed(false); } } @@ -264,11 +264,11 @@ void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, int p_index, } void VisualShaderGraphPlugin::update_uniform_refs() { - for (Map<int, Link>::Element *E = links.front(); E; E = E->next()) { - VisualShaderNodeUniformRef *ref = Object::cast_to<VisualShaderNodeUniformRef>(E->get().visual_node); + for (KeyValue<int, Link> &E : links) { + VisualShaderNodeUniformRef *ref = Object::cast_to<VisualShaderNodeUniformRef>(E.value.visual_node); if (ref) { - remove_node(E->get().type, E->key()); - add_node(E->get().type, E->key()); + remove_node(E.value.type, E.key); + add_node(E.value.type, E.key); } } } @@ -491,6 +491,35 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { bool is_curve = curve.is_valid() || curve_xyz.is_valid(); if (is_curve) { + // a default value handling + { + Variant default_value; + bool port_left_used = false; + + for (const VisualShader::Connection &E : connections) { + if (E.to_node == p_id && E.to_port == 0) { + port_left_used = true; + break; + } + } + + if (!port_left_used) { + default_value = vsnode->get_input_port_default_value(0); + } + + Button *button = memnew(Button); + custom_editor->add_child(button); + register_default_input_button(p_id, 0, button); + custom_editor->move_child(button, 0); + + button->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_edit_port_default_input), varray(button, p_id, 0)); + if (default_value.get_type() != Variant::NIL) { + set_input_port_default_value(p_type, p_id, 0, default_value); + } else { + button->hide(); + } + } + VisualShaderEditor::get_singleton()->graph->add_child(node); VisualShaderEditor::get_singleton()->_update_created_node(node); @@ -643,6 +672,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { for (const VisualShader::Connection &E : connections) { if (E.to_node == p_id && E.to_port == j) { port_left_used = true; + break; } } } @@ -777,7 +807,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/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp index 5bbc0c9dd5..9a44d40dcb 100644 --- a/editor/plugins/voxel_gi_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -33,7 +33,7 @@ void VoxelGIEditorPlugin::_bake() { if (voxel_gi) { if (voxel_gi->get_probe_data().is_null()) { - String path = get_tree()->get_edited_scene_root()->get_filename(); + String path = get_tree()->get_edited_scene_root()->get_scene_file_path(); if (path == String()) { path = "res://" + voxel_gi->get_name() + "_data.res"; } else { diff --git a/editor/progress_dialog.cpp b/editor/progress_dialog.cpp index 3441060fad..95a5646013 100644 --- a/editor/progress_dialog.cpp +++ b/editor/progress_dialog.cpp @@ -63,9 +63,9 @@ void BackgroundProgress::_add_task(const String &p_task, const String &p_label, void BackgroundProgress::_update() { _THREAD_SAFE_METHOD_ - for (Map<String, int>::Element *E = updates.front(); E; E = E->next()) { - if (tasks.has(E->key())) { - _task_step(E->key(), E->get()); + for (const KeyValue<String, int> &E : updates) { + if (tasks.has(E.key)) { + _task_step(E.key, E.value); } } diff --git a/editor/project_export.cpp b/editor/project_export.cpp index 14158b02c8..ad9c81458f 100644 --- a/editor/project_export.cpp +++ b/editor/project_export.cpp @@ -752,6 +752,9 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem if (p_only_scenes && type != "PackedScene") { continue; } + if (type == "TextFile") { + continue; + } TreeItem *file = include_files->create_item(p_item); file->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 81554c9550..e8fd3070c2 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -2123,8 +2123,8 @@ void ProjectManager::_run_project_confirm() { const String &selected = selected_list[i].project_key; String path = EditorSettings::get_singleton()->get("projects/" + selected); - // `.substr(6)` on `IMPORTED_FILES_PATH` strips away the leading "res://". - if (!DirAccess::exists(path.plus_file(ProjectSettings::IMPORTED_FILES_PATH.substr(6)))) { + // `.substr(6)` on `ProjectSettings::get_singleton()->get_imported_files_path()` strips away the leading "res://". + if (!DirAccess::exists(path.plus_file(ProjectSettings::get_singleton()->get_imported_files_path().substr(6)))) { run_error_diag->set_text(TTR("Can't run project: Assets need to be imported.\nPlease edit the project to trigger the initial import.")); run_error_diag->popup_centered(); continue; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 4bc0905163..4a59eb4fb3 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -187,8 +187,8 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N break; } - if (edited_scene->get_filename() != "") { - if (_cyclical_dependency_exists(edited_scene->get_filename(), instantiated_scene)) { + if (edited_scene->get_scene_file_path() != "") { + if (_cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { accept->set_text(vformat(TTR("Cannot instance the scene '%s' because the current scene exists within one of its nodes."), p_files[i])); accept->popup_centered(); error = true; @@ -196,7 +196,7 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N } } - instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_files[i])); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_files[i])); instances.push_back(instantiated_scene); } @@ -307,7 +307,7 @@ bool SceneTreeDock::_track_inherit(const String &p_target_scene_path, Node *p_de bool result = false; Vector<Node *> instances; while (true) { - if (p->get_filename() == p_target_scene_path) { + if (p->get_scene_file_path() == p_target_scene_path) { result = true; break; } @@ -411,10 +411,6 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } } break; case TOOL_EXPAND_COLLAPSE: { - if (!scene_tree->get_selected()) { - break; - } - Tree *tree = scene_tree->get_scene_tree(); TreeItem *selected_item = tree->get_selected(); @@ -442,7 +438,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { if (!node_clipboard.is_empty()) { _clear_clipboard(); } - clipboard_source_scene = editor->get_edited_scene()->get_filename(); + clipboard_source_scene = editor->get_edited_scene()->get_scene_file_path(); selection.sort_custom<Node::Comparator>(); @@ -465,9 +461,9 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } bool has_cycle = false; - if (edited_scene->get_filename() != String()) { + if (edited_scene->get_scene_file_path() != String()) { for (Node *E : node_clipboard) { - if (edited_scene->get_filename() == E->get_filename()) { + if (edited_scene->get_scene_file_path() == E->get_scene_file_path()) { has_cycle = true; break; } @@ -496,7 +492,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { editor_data->get_undo_redo().add_do_method(editor_selection, "clear"); Map<RES, RES> resource_remap; - String target_scene = editor->get_edited_scene()->get_filename(); + String target_scene = editor->get_edited_scene()->get_scene_file_path(); if (target_scene != clipboard_source_scene) { if (!clipboard_resource_remap.has(target_scene)) { Map<RES, RES> remap; @@ -517,8 +513,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { editor_data->get_undo_redo().add_do_method(paste_parent, "add_child", dup); - for (Map<const Node *, Node *>::Element *E2 = duplimap.front(); E2; E2 = E2->next()) { - Node *d = E2->value(); + for (KeyValue<const Node *, Node *> &E2 : duplimap) { + Node *d = E2.value; editor_data->get_undo_redo().add_do_method(d, "set_owner", owner); } @@ -808,7 +804,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { return; } - if (node->get_filename() != String()) { + if (node->get_scene_file_path() != String()) { accept->set_text(TTR("Instantiated scenes can't become root")); accept->popup_centered(); return; @@ -818,14 +814,14 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { editor_data->get_undo_redo().add_do_method(node->get_parent(), "remove_child", node); editor_data->get_undo_redo().add_do_method(editor, "set_edited_scene", node); editor_data->get_undo_redo().add_do_method(node, "add_child", root); - editor_data->get_undo_redo().add_do_method(node, "set_filename", root->get_filename()); - editor_data->get_undo_redo().add_do_method(root, "set_filename", String()); + editor_data->get_undo_redo().add_do_method(node, "set_scene_file_path", root->get_scene_file_path()); + editor_data->get_undo_redo().add_do_method(root, "set_scene_file_path", String()); editor_data->get_undo_redo().add_do_method(node, "set_owner", (Object *)nullptr); editor_data->get_undo_redo().add_do_method(root, "set_owner", node); _node_replace_owner(root, root, node, MODE_DO); - editor_data->get_undo_redo().add_undo_method(root, "set_filename", root->get_filename()); - editor_data->get_undo_redo().add_undo_method(node, "set_filename", String()); + editor_data->get_undo_redo().add_undo_method(root, "set_scene_file_path", root->get_scene_file_path()); + editor_data->get_undo_redo().add_undo_method(node, "set_scene_file_path", String()); editor_data->get_undo_redo().add_undo_method(node, "remove_child", root); editor_data->get_undo_redo().add_undo_method(editor, "set_edited_scene", root); editor_data->get_undo_redo().add_undo_method(node->get_parent(), "add_child", node); @@ -848,8 +844,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { break; } Ref<MultiNodeEdit> mne = memnew(MultiNodeEdit); - for (const Map<Node *, Object *>::Element *E = EditorNode::get_singleton()->get_editor_selection()->get_selection().front(); E; E = E->next()) { - mne->add_node(root->get_path_to(E->key())); + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + mne->add_node(root->get_path_to(E.key)); } EditorNode::get_singleton()->push_item(mne.ptr()); @@ -887,7 +883,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { Node *node = remove_list[0]; if (node == editor_data->get_edited_scene_root()) { msg = vformat(TTR("Delete the root node \"%s\"?"), node->get_name()); - } else if (node->get_filename() == "" && node->get_child_count() > 0) { + } else if (node->get_scene_file_path() == "" && node->get_child_count() > 0) { // Display this message only for non-instantiated scenes msg = vformat(TTR("Delete node \"%s\" and its children?"), node->get_name()); } else { @@ -934,7 +930,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { break; } - if (tocopy != editor_data->get_edited_scene_root() && tocopy->get_filename() != "") { + if (tocopy != editor_data->get_edited_scene_root() && tocopy->get_scene_file_path() != "") { accept->set_text(TTR("Can't save the branch of an already instantiated scene.\nTo create a variation of a scene, you can make an inherited scene based on the instantiated scene using Scene > New Inherited Scene... instead.")); accept->popup_centered(); break; @@ -979,6 +975,9 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); } break; + case TOOL_AUTO_EXPAND: { + scene_tree->set_auto_expand_selected(!EditorSettings::get_singleton()->get("docks/scene_tree/auto_expand_to_selected"), true); + } break; case TOOL_SCENE_EDITABLE_CHILDREN: { if (!profile_allow_editing) { break; @@ -1047,10 +1046,10 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { break; } - ERR_FAIL_COND(node->get_filename() == String()); + ERR_FAIL_COND(node->get_scene_file_path() == String()); undo_redo->create_action(TTR("Make Local")); - undo_redo->add_do_method(node, "set_filename", ""); - undo_redo->add_undo_method(node, "set_filename", node->get_filename()); + undo_redo->add_do_method(node, "set_scene_file_path", ""); + undo_redo->add_undo_method(node, "set_scene_file_path", node->get_scene_file_path()); _node_replace_owner(node, node, root); undo_redo->add_do_method(scene_tree, "update_tree"); undo_redo->add_undo_method(scene_tree, "update_tree"); @@ -1064,7 +1063,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { if (e) { Node *node = e->get(); if (node) { - scene_tree->emit_signal(SNAME("open"), node->get_filename()); + scene_tree->emit_signal(SNAME("open"), node->get_scene_file_path()); } } } break; @@ -1223,6 +1222,7 @@ void SceneTreeDock::_notification(int p_what) { button_instance->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); button_create_script->set_icon(get_theme_icon(SNAME("ScriptCreate"), SNAME("EditorIcons"))); button_detach_script->set_icon(get_theme_icon(SNAME("ScriptRemove"), SNAME("EditorIcons"))); + button_tree_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); filter->set_clear_button_enabled(true); @@ -1291,12 +1291,14 @@ void SceneTreeDock::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { clear_inherit_confirm->connect("confirmed", callable_mp(this, &SceneTreeDock::_tool_selected), make_binds(TOOL_SCENE_CLEAR_INHERITANCE_CONFIRM, false)); + scene_tree->set_auto_expand_selected(EditorSettings::get_singleton()->get("docks/scene_tree/auto_expand_to_selected"), false); } break; case NOTIFICATION_EXIT_TREE: { clear_inherit_confirm->disconnect("confirmed", callable_mp(this, &SceneTreeDock::_tool_selected)); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + scene_tree->set_auto_expand_selected(EditorSettings::get_singleton()->get("docks/scene_tree/auto_expand_to_selected"), false); button_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); button_instance->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); button_create_script->set_icon(get_theme_icon(SNAME("ScriptCreate"), SNAME("EditorIcons"))); @@ -1694,7 +1696,7 @@ bool SceneTreeDock::_validate_no_instance() { List<Node *> selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { - if (E != edited_scene && E->get_filename() != "") { + if (E != edited_scene && E->get_scene_file_path() != "") { accept->set_text(TTR("This operation can't be done on instantiated scenes.")); accept->popup_centered(); return false; @@ -1833,8 +1835,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V editor_data->get_undo_redo().add_do_method(this, "_set_owners", edited_scene, owners); - if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) { - editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node); + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == node) { + editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node); } editor_data->get_undo_redo().add_undo_method(new_parent, "remove_child", node); @@ -1859,8 +1861,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V editor_data->get_undo_redo().add_undo_method(node->get_parent(), "add_child", node); editor_data->get_undo_redo().add_undo_method(node->get_parent(), "move_child", node, child_pos); editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners); - if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) { - editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node); + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == node) { + editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node); } if (p_keep_global_xform) { @@ -2071,8 +2073,8 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { editor_data->get_undo_redo().add_do_method(n->get_parent(), "remove_child", n); editor_data->get_undo_redo().add_undo_method(n->get_parent(), "add_child", n); editor_data->get_undo_redo().add_undo_method(n->get_parent(), "move_child", n, n->get_index()); - if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == n) { - editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", n); + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == n) { + editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", n); } editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners); editor_data->get_undo_redo().add_undo_reference(n); @@ -2101,11 +2103,11 @@ void SceneTreeDock::_update_script_button() { if (!profile_allow_script_editing) { button_create_script->hide(); button_detach_script->hide(); - } else if (EditorNode::get_singleton()->get_editor_selection()->get_selection().size() == 0) { + } else if (editor_selection->get_selection().size() == 0) { button_create_script->hide(); button_detach_script->hide(); - } else if (EditorNode::get_singleton()->get_editor_selection()->get_selection().size() == 1) { - Node *n = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list()[0]; + } else if (editor_selection->get_selection().size() == 1) { + Node *n = editor_selection->get_selected_node_list()[0]; if (n->get_script().is_null()) { button_create_script->show(); button_detach_script->hide(); @@ -2128,10 +2130,12 @@ void SceneTreeDock::_update_script_button() { } void SceneTreeDock::_selection_changed() { - int selection_size = EditorNode::get_singleton()->get_editor_selection()->get_selection().size(); + int selection_size = editor_selection->get_selection().size(); if (selection_size > 1) { //automatically turn on multi-edit _tool_selected(TOOL_MULTI_EDIT); + } else if (selection_size == 1) { + editor->push_item(editor_selection->get_selection().front()->key()); } else if (selection_size == 0) { editor->push_item(nullptr); } @@ -2692,7 +2696,6 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_icon_shortcut(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/add_child_node"), TOOL_NEW); menu->add_icon_shortcut(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/instance_scene"), TOOL_INSTANTIATE); } - menu->add_icon_shortcut(get_theme_icon(SNAME("Collapse"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/expand_collapse_all"), TOOL_EXPAND_COLLAPSE); menu->add_separator(); existing_script = selected->get_script(); @@ -2751,7 +2754,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { bool can_replace = true; for (Node *E : selection) { - if (E != edited_scene && (E->get_owner() != edited_scene || E->get_filename() != "")) { + if (E != edited_scene && (E->get_owner() != edited_scene || E->get_scene_file_path() != "")) { can_replace = false; break; } @@ -2783,7 +2786,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_icon_shortcut(get_theme_icon(SNAME("CopyNodePath"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/copy_node_path"), TOOL_COPY_NODE_PATH); } - bool is_external = (selection[0]->get_filename() != ""); + bool is_external = (selection[0]->get_scene_file_path() != ""); if (is_external) { bool is_inherited = selection[0]->get_scene_inherited_state() != nullptr; bool is_top_level = selection[0]->get_owner() == nullptr; @@ -2830,6 +2833,18 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->popup(); } +void SceneTreeDock::_open_tree_menu() { + menu->clear(); + + menu->add_icon_shortcut(get_theme_icon(SNAME("Collapse"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/expand_collapse_all"), TOOL_EXPAND_COLLAPSE); + menu->add_check_item(TTR("Auto Expand to Selected"), TOOL_AUTO_EXPAND); + menu->set_item_checked(menu->get_item_idx_from_text(TTR("Auto Expand to Selected")), EditorSettings::get_singleton()->get("docks/scene_tree/auto_expand_to_selected")); + + menu->set_size(Size2(1, 1)); + menu->set_position(get_screen_position() + get_local_mouse_position()); + menu->popup(); +} + void SceneTreeDock::_filter_changed(const String &p_filter) { scene_tree->set_filter(p_filter); } @@ -2883,9 +2898,9 @@ void SceneTreeDock::attach_script_to_selected(bool p_extend) { Ref<Script> existing = selected->get_script(); - String path = selected->get_filename(); + String path = selected->get_scene_file_path(); if (path == "") { - String root_path = editor_data->get_edited_scene_root()->get_filename(); + String root_path = editor_data->get_edited_scene_root()->get_scene_file_path(); if (root_path == "") { path = String("res://").plus_file(selected->get_name()); } else { @@ -2937,7 +2952,7 @@ void SceneTreeDock::attach_shader_to_selected() { if (path == "") { String root_path; if (editor_data->get_edited_scene_root()) { - root_path = editor_data->get_edited_scene_root()->get_filename(); + root_path = editor_data->get_edited_scene_root()->get_scene_file_path(); } String shader_name; if (selected_shader_material->get_name().is_empty()) { @@ -3269,6 +3284,11 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel filter_hbc->add_child(button_detach_script); button_detach_script->hide(); + button_tree_menu = memnew(Button); + button_tree_menu->set_flat(true); + button_tree_menu->connect("pressed", callable_mp(this, &SceneTreeDock::_open_tree_menu)); + filter_hbc->add_child(button_tree_menu); + button_hb = memnew(HBoxContainer); vbc->add_child(button_hb); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index ece0ca5ca4..255e026887 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -81,6 +81,7 @@ class SceneTreeDock : public VBoxContainer { TOOL_COPY_NODE_PATH, TOOL_BUTTON_MAX, TOOL_OPEN_DOCUMENTATION, + TOOL_AUTO_EXPAND, TOOL_SCENE_EDITABLE_CHILDREN, TOOL_SCENE_USE_PLACEHOLDER, TOOL_SCENE_MAKE_LOCAL, @@ -115,6 +116,7 @@ class SceneTreeDock : public VBoxContainer { Button *button_instance; Button *button_create_script; Button *button_detach_script; + Button *button_tree_menu; Button *button_2d; Button *button_3d; @@ -237,6 +239,7 @@ class SceneTreeDock : public VBoxContainer { void _quick_open(); void _tree_rmb(const Vector2 &p_menu_pos); + void _open_tree_menu(); void _filter_changed(const String &p_filter); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 3de87d9df7..73523474ef 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -66,7 +66,7 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i emit_signal(SNAME("open"), n->get_scene_inherited_state()->get_path()); } } else { - emit_signal(SNAME("open"), n->get_filename()); + emit_signal(SNAME("open"), n->get_scene_file_path()); } } else if (p_id == BUTTON_SCRIPT) { Ref<Script> script_typed = n->get_script(); @@ -102,7 +102,7 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i undo_redo->commit_action(); } else if (p_id == BUTTON_PIN) { if (n->is_class("AnimationPlayer")) { - AnimationPlayerEditor::singleton->unpin(); + AnimationPlayerEditor::get_singleton()->unpin(); _update_tree(); } @@ -302,10 +302,10 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } item->set_tooltip(0, tooltip); - } else if (p_node != get_scene_node() && p_node->get_filename() != "" && can_open_instance) { + } else if (p_node != get_scene_node() && p_node->get_scene_file_path() != "" && can_open_instance) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = String(p_node->get_name()) + "\n" + TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Instance:") + " " + p_node->get_scene_file_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -386,7 +386,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll _update_visibility_color(p_node, item); } else if (p_node->is_class("AnimationPlayer")) { - bool is_pinned = AnimationPlayerEditor::singleton->get_player() == p_node && AnimationPlayerEditor::singleton->is_pinned(); + bool is_pinned = AnimationPlayerEditor::get_singleton()->get_player() == p_node && AnimationPlayerEditor::get_singleton()->is_pinned(); if (is_pinned) { item->add_button(0, get_theme_icon(SNAME("Pin"), SNAME("EditorIcons")), BUTTON_PIN, false, TTR("AnimationPlayer is pinned.\nClick to unpin.")); @@ -527,7 +527,7 @@ void SceneTreeEditor::_node_removed(Node *p_node) { } void SceneTreeEditor::_node_renamed(Node *p_node) { - if (!get_scene_node()->is_ancestor_of(p_node)) { + if (p_node != get_scene_node() && !get_scene_node()->is_ancestor_of(p_node)) { return; } @@ -737,17 +737,18 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) { TreeItem *item = p_node ? _find(tree->get_root(), p_node->get_path()) : nullptr; if (item) { - // make visible when it's collapsed - TreeItem *node = item->get_parent(); - while (node && node != tree->get_root()) { - node->set_collapsed(false); - node = node->get_parent(); + if (auto_expand_selected) { + // Make visible when it's collapsed. + TreeItem *node = item->get_parent(); + while (node && node != tree->get_root()) { + node->set_collapsed(false); + node = node->get_parent(); + } + item->select(0); + item->set_as_cursor(0); + selected = p_node; + tree->ensure_cursor_is_visible(); } - item->select(0); - item->set_as_cursor(0); - selected = p_node; - tree->ensure_cursor_is_visible(); - } else { if (!p_node) { selected = nullptr; @@ -954,7 +955,7 @@ Variant SceneTreeEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from Node *n = get_node(np); if (n) { // Only allow selection if not part of an instantiated scene. - if (!n->get_owner() || n->get_owner() == get_scene_node() || n->get_owner()->get_filename() == String()) { + if (!n->get_owner() || n->get_owner() == get_scene_node() || n->get_owner()->get_scene_file_path() == String()) { selected.push_back(n); icons.push_back(next->get_icon(0)); } @@ -1127,11 +1128,19 @@ void SceneTreeEditor::_rmb_select(const Vector2 &p_pos) { void SceneTreeEditor::update_warning() { _warning_changed(nullptr); } + void SceneTreeEditor::_warning_changed(Node *p_for_node) { //should use a timer update_timer->start(); } +void SceneTreeEditor::set_auto_expand_selected(bool p_auto, bool p_update_settings) { + if (p_update_settings) { + EditorSettings::get_singleton()->set("docks/scene_tree/auto_expand_to_selected", p_auto); + } + auto_expand_selected = p_auto; +} + void SceneTreeEditor::set_connect_to_script_mode(bool p_enable) { connect_to_script_mode = p_enable; update_tree(); diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index acd49e8d92..4acd5d8486 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -64,6 +64,7 @@ class SceneTreeEditor : public Control { AcceptDialog *error; AcceptDialog *warning; + bool auto_expand_selected = true; bool connect_to_script_mode; bool connecting_signal; @@ -152,6 +153,7 @@ public: void update_tree() { _update_tree(); } + void set_auto_expand_selected(bool p_auto, bool p_update_settings); void set_connect_to_script_mode(bool p_enable); void set_connecting_signal(bool p_enable); diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 4cbc859e0c..1e19d9bd47 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -425,8 +425,8 @@ void ScriptCreateDialog::_lang_changed(int l) { templates[i].id = new_id; } // Disable overridden - for (Map<String, Vector<int>>::Element *E = template_overrides.front(); E; E = E->next()) { - const Vector<int> &overrides = E->get(); + for (const KeyValue<String, Vector<int>> &E : template_overrides) { + const Vector<int> &overrides = E.value; if (overrides.size() == 1) { continue; // doesn't override anything diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index f024589ef1..78dc85aa7d 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -193,35 +193,25 @@ void EditorSettingsDialog::_event_config_confirmed() { return; } - if (editing_action) { - if (current_action_event_index == -1) { - // Add new event - current_action_events.push_back(k); - } else { - // Edit existing event - current_action_events[current_action_event_index] = k; - } + if (current_event_index == -1) { + // Add new event + current_events.push_back(k); + } else { + // Edit existing event + current_events[current_event_index] = k; + } - _update_builtin_action(current_action, current_action_events); + if (is_editing_action) { + _update_builtin_action(current_edited_identifier, current_events); } else { - k = k->duplicate(); - Ref<Shortcut> current_sc = EditorSettings::get_singleton()->get_shortcut(shortcut_being_edited); - - undo_redo->create_action(TTR("Change Shortcut") + " '" + shortcut_being_edited + "'"); - undo_redo->add_do_method(current_sc.ptr(), "set_event", k); - undo_redo->add_undo_method(current_sc.ptr(), "set_event", current_sc->get_event()); - undo_redo->add_do_method(this, "_update_shortcuts"); - undo_redo->add_undo_method(this, "_update_shortcuts"); - undo_redo->add_do_method(this, "_settings_changed"); - undo_redo->add_undo_method(this, "_settings_changed"); - undo_redo->commit_action(); + _update_shortcut_events(current_edited_identifier, current_events); } } void EditorSettingsDialog::_update_builtin_action(const String &p_name, const Array &p_events) { - Array old_input_array = EditorSettings::get_singleton()->get_builtin_action_overrides(current_action); + Array old_input_array = EditorSettings::get_singleton()->get_builtin_action_overrides(p_name); - undo_redo->create_action(TTR("Edit Built-in Action")); + undo_redo->create_action(TTR("Edit Built-in Action") + " '" + p_name + "'"); undo_redo->add_do_method(EditorSettings::get_singleton(), "set_builtin_action_override", p_name, p_events); undo_redo->add_undo_method(EditorSettings::get_singleton(), "set_builtin_action_override", p_name, old_input_array); undo_redo->add_do_method(this, "_settings_changed"); @@ -231,15 +221,125 @@ void EditorSettingsDialog::_update_builtin_action(const String &p_name, const Ar _update_shortcuts(); } +void EditorSettingsDialog::_update_shortcut_events(const String &p_path, const Array &p_events) { + Ref<Shortcut> current_sc = EditorSettings::get_singleton()->get_shortcut(p_path); + + undo_redo->create_action(TTR("Edit Shortcut") + " '" + p_path + "'"); + undo_redo->add_do_method(current_sc.ptr(), "set_events", p_events); + undo_redo->add_undo_method(current_sc.ptr(), "set_events", current_sc->get_events()); + undo_redo->add_do_method(this, "_update_shortcuts"); + undo_redo->add_undo_method(this, "_update_shortcuts"); + undo_redo->add_do_method(this, "_settings_changed"); + undo_redo->add_undo_method(this, "_settings_changed"); + undo_redo->commit_action(); +} + +Array EditorSettingsDialog::_event_list_to_array_helper(List<Ref<InputEvent>> &p_events) { + Array events; + + // Convert the list to an array, and only keep key events as this is for the editor. + for (List<Ref<InputEvent>>::Element *E = p_events.front(); E; E = E->next()) { + Ref<InputEventKey> k = E->get(); + if (k.is_valid()) { + events.append(E->get()); + } + } + + return events; +} + +void EditorSettingsDialog::_create_shortcut_treeitem(TreeItem *p_parent, const String &p_shortcut_identifier, const String &p_display, Array &p_events, bool p_allow_revert, bool p_is_action, bool p_is_collapsed) { + TreeItem *shortcut_item = shortcuts->create_item(p_parent); + shortcut_item->set_collapsed(p_is_collapsed); + shortcut_item->set_text(0, p_display); + + Ref<InputEvent> primary = p_events.size() > 0 ? Ref<InputEvent>(p_events[0]) : Ref<InputEvent>(); + Ref<InputEvent> secondary = p_events.size() > 1 ? Ref<InputEvent>(p_events[1]) : Ref<InputEvent>(); + + String sc_text = "None"; + if (primary.is_valid()) { + sc_text = primary->as_text(); + + if (secondary.is_valid()) { + sc_text += ", " + secondary->as_text(); + + if (p_events.size() > 2) { + sc_text += " (+" + itos(p_events.size() - 2) + ")"; + } + } + } + + shortcut_item->set_text(1, sc_text); + if (sc_text == "None") { + // Fade out unassigned shortcut labels for easier visual grepping. + shortcut_item->set_custom_color(1, shortcuts->get_theme_color("font_color", "Label") * Color(1, 1, 1, 0.5)); + } + + if (p_allow_revert) { + shortcut_item->add_button(1, shortcuts->get_theme_icon("Reload", "EditorIcons"), SHORTCUT_REVERT); + } + + shortcut_item->add_button(1, shortcuts->get_theme_icon("Add", "EditorIcons"), SHORTCUT_ADD); + shortcut_item->add_button(1, shortcuts->get_theme_icon("Close", "EditorIcons"), SHORTCUT_ERASE); + + shortcut_item->set_meta("is_action", p_is_action); + shortcut_item->set_meta("type", "shortcut"); + shortcut_item->set_meta("shortcut_identifier", p_shortcut_identifier); + shortcut_item->set_meta("events", p_events); + + // Shortcut Input Events + for (int i = 0; i < p_events.size(); i++) { + Ref<InputEvent> ie = p_events[i]; + if (ie.is_null()) { + continue; + } + + TreeItem *event_item = shortcuts->create_item(shortcut_item); + + event_item->set_text(0, shortcut_item->get_child_count() == 1 ? "Primary" : ""); + event_item->set_text(1, ie->as_text()); + + event_item->add_button(1, shortcuts->get_theme_icon("Edit", "EditorIcons"), SHORTCUT_EDIT); + event_item->add_button(1, shortcuts->get_theme_icon("Close", "EditorIcons"), SHORTCUT_ERASE); + + event_item->set_custom_bg_color(0, shortcuts->get_theme_color("dark_color_3", "Editor")); + event_item->set_custom_bg_color(1, shortcuts->get_theme_color("dark_color_3", "Editor")); + + event_item->set_meta("is_action", p_is_action); + event_item->set_meta("type", "event"); + event_item->set_meta("event_index", i); + } +} + void EditorSettingsDialog::_update_shortcuts() { // Before clearing the tree, take note of which categories are collapsed so that this state can be maintained when the tree is repopulated. Map<String, bool> collapsed; if (shortcuts->get_root() && shortcuts->get_root()->get_first_child()) { - for (TreeItem *item = shortcuts->get_root()->get_first_child(); item; item = item->get_next()) { - collapsed[item->get_text(0)] = item->is_collapsed(); + TreeItem *ti = shortcuts->get_root()->get_first_child(); + while (ti) { + // Not all items have valid or unique text in the first column - so if it has an identifier, use that, as it should be unique. + if (ti->get_first_child() && ti->has_meta("shortcut_identifier")) { + collapsed[ti->get_meta("shortcut_identifier")] = ti->is_collapsed(); + } else { + collapsed[ti->get_text(0)] = ti->is_collapsed(); + } + + // Try go down tree + TreeItem *ti_next = ti->get_first_child(); + // Try go across tree + if (!ti_next) { + ti_next = ti->get_next(); + } + // Try go up tree, to next node + if (!ti_next) { + ti_next = ti->get_parent()->get_next(); + } + + ti = ti_next; } } + shortcuts->clear(); TreeItem *root = shortcuts->create_item(); @@ -247,7 +347,6 @@ void EditorSettingsDialog::_update_shortcuts() { // Set up section for Common/Built-in actions TreeItem *common_section = shortcuts->create_item(root); - sections["Common"] = common_section; common_section->set_text(0, TTR("Common")); common_section->set_selectable(0, false); @@ -262,7 +361,6 @@ void EditorSettingsDialog::_update_shortcuts() { OrderedHashMap<StringName, InputMap::Action> action_map = InputMap::get_singleton()->get_action_map(); for (OrderedHashMap<StringName, InputMap::Action>::Element E = action_map.front(); E; E = E.next()) { String action_name = E.key(); - InputMap::Action action = E.get(); Array events; // Need to get the list of events into an array so it can be set as metadata on the item. @@ -278,21 +376,6 @@ void EditorSettingsDialog::_update_shortcuts() { } } - bool same_as_defaults = key_default_events.size() == action.inputs.size(); // Initially this is set to just whether the arrays are equal. Later we check the events if needed. - - int count = 0; - for (List<Ref<InputEvent>>::Element *I = action.inputs.front(); I; I = I->next()) { - // Add event and event text to respective arrays. - events.push_back(I->get()); - event_strings.push_back(I->get()->as_text()); - - // Only check if the events have been the same so far - once one fails, we don't need to check any more. - if (same_as_defaults && !key_default_events[count]->is_match(I->get())) { - same_as_defaults = false; - } - count++; - } - // Join the text of the events with a delimiter so they can all be displayed in one cell. String events_display_string = event_strings.is_empty() ? "None" : String("; ").join(event_strings); @@ -300,25 +383,12 @@ void EditorSettingsDialog::_update_shortcuts() { continue; } - TreeItem *item = shortcuts->create_item(common_section); - item->set_text(0, action_name); - item->set_text(1, events_display_string); - - if (!same_as_defaults) { - item->add_button(1, shortcuts->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")), 2); - } - - if (events_display_string == "None") { - // Fade out unassigned shortcut labels for easier visual grepping. - item->set_custom_color(1, shortcuts->get_theme_color(SNAME("font_color"), SNAME("Label")) * Color(1, 1, 1, 0.5)); - } + Array action_events = _event_list_to_array_helper(action.inputs); + Array default_events = _event_list_to_array_helper(all_default_events); + bool same_as_defaults = Shortcut::is_event_array_equal(default_events, action_events); + bool collapse = !collapsed.has(action_name) || (collapsed.has(action_name) && collapsed[action_name]); - item->add_button(1, shortcuts->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), 0); - item->add_button(1, shortcuts->get_theme_icon(SNAME("Close"), SNAME("EditorIcons")), 1); - item->set_tooltip(0, action_name); - item->set_tooltip(1, events_display_string); - item->set_metadata(0, "Common"); - item->set_metadata(1, events); + _create_shortcut_treeitem(common_section, action_name, action_name, action_events, !same_as_defaults, true, collapse); } // Editor Shortcuts @@ -332,11 +402,10 @@ void EditorSettingsDialog::_update_shortcuts() { continue; } - Ref<InputEvent> original = sc->get_meta("original"); - - String section_name = E.get_slice("/", 0); + // Shortcut Section TreeItem *section; + String section_name = E.get_slice("/", 0); if (sections.has(section_name)) { section = sections[section_name]; @@ -357,33 +426,23 @@ void EditorSettingsDialog::_update_shortcuts() { sections[section_name] = section; } - // Don't match unassigned shortcuts when searching for assigned keys in search results. - // This prevents all unassigned shortcuts from appearing when searching a string like "no". - if (shortcut_filter.is_subsequence_ofi(sc->get_name()) || (sc->get_as_text() != "None" && shortcut_filter.is_subsequence_ofi(sc->get_as_text()))) { - TreeItem *item = shortcuts->create_item(section); - - item->set_text(0, sc->get_name()); - item->set_text(1, sc->get_as_text()); + // Shortcut Item - if (!sc->matches_event(original) && !(sc->get_event().is_null() && original.is_null())) { - item->add_button(1, shortcuts->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")), 2); - } + if (!shortcut_filter.is_subsequence_ofi(sc->get_name())) { + continue; + } - if (sc->get_as_text() == "None") { - // Fade out unassigned shortcut labels for easier visual grepping. - item->set_custom_color(1, shortcuts->get_theme_color(SNAME("font_color"), SNAME("Label")) * Color(1, 1, 1, 0.5)); - } + Array original = sc->get_meta("original"); + 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]); - item->add_button(1, shortcuts->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), 0); - item->add_button(1, shortcuts->get_theme_icon(SNAME("Close"), SNAME("EditorIcons")), 1); - item->set_tooltip(0, E); - item->set_metadata(0, E); - } + _create_shortcut_treeitem(section, E, sc->get_name(), shortcuts_array, !same_as_defaults, false, collapse); } // remove sections with no shortcuts - for (Map<String, TreeItem *>::Element *E = sections.front(); E; E = E->next()) { - TreeItem *section = E->get(); + for (KeyValue<String, TreeItem *> &E : sections) { + TreeItem *section = E.value; if (section->get_first_child() == nullptr) { root->remove_child(section); } @@ -392,123 +451,130 @@ void EditorSettingsDialog::_update_shortcuts() { void EditorSettingsDialog::_shortcut_button_pressed(Object *p_item, int p_column, int p_idx) { TreeItem *ti = Object::cast_to<TreeItem>(p_item); - ERR_FAIL_COND(!ti); - - button_idx = p_idx; + ERR_FAIL_COND_MSG(!ti, "Object passed is not a TreeItem"); - if (ti->get_metadata(0) == "Common") { - // Editing a Built-in action, which can have multiple bindings. - editing_action = true; - current_action = ti->get_text(0); + ShortcutButton button_idx = (ShortcutButton)p_idx; - switch (button_idx) { - case SHORTCUT_REVERT: { - Array events; - List<Ref<InputEvent>> defaults = InputMap::get_singleton()->get_builtins_with_feature_overrides_applied()[current_action]; + is_editing_action = ti->get_meta("is_action"); - // Convert the list to an array, and only keep key events as this is for the editor. - for (const Ref<InputEvent> &k : defaults) { - if (k.is_valid()) { - events.append(k); - } - } + String type = ti->get_meta("type"); - _update_builtin_action(current_action, events); - } break; - case SHORTCUT_EDIT: - case SHORTCUT_ERASE: { - // For Edit end Delete, we will show a popup which displays each event so the user can select which one to edit/delete. - current_action_events = ti->get_metadata(1); - action_popup->clear(); - - for (int i = 0; i < current_action_events.size(); i++) { - Ref<InputEvent> ie = current_action_events[i]; - action_popup->add_item(ie->as_text()); - action_popup->set_item_metadata(i, ie); - } - - if (button_idx == SHORTCUT_EDIT) { - // If editing, add a button which can be used to add an additional event. - action_popup->add_icon_item(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), TTR("Add")); - } + if (type == "event") { + current_edited_identifier = ti->get_parent()->get_meta("shortcut_identifier"); + current_events = ti->get_parent()->get_meta("events"); + current_event_index = ti->get_meta("event_index"); + } else { // Type is "shortcut" + current_edited_identifier = ti->get_meta("shortcut_identifier"); + current_events = ti->get_meta("events"); + current_event_index = -1; + } - action_popup->set_position(get_position() + get_mouse_position()); - action_popup->take_mouse_focus(); - action_popup->popup(); - action_popup->set_as_minsize(); - } break; - default: - break; - } - } else { - // Editing an Editor Shortcut, which can only have 1 binding. - String item = ti->get_metadata(0); - Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(item); - editing_action = false; - - switch (button_idx) { - case EditorSettingsDialog::SHORTCUT_EDIT: - shortcut_editor->popup_and_configure(sc->get_event()); - shortcut_being_edited = item; - break; - case EditorSettingsDialog::SHORTCUT_ERASE: { - if (!sc.is_valid()) { - return; //pointless, there is nothing + switch (button_idx) { + case EditorSettingsDialog::SHORTCUT_ADD: { + // Only for "shortcut" types + shortcut_editor->popup_and_configure(); + } break; + case EditorSettingsDialog::SHORTCUT_EDIT: { + // Only for "event" types + shortcut_editor->popup_and_configure(current_events[current_event_index]); + } break; + case EditorSettingsDialog::SHORTCUT_ERASE: { + if (type == "shortcut") { + if (is_editing_action) { + _update_builtin_action(current_edited_identifier, Array()); + } else { + _update_shortcut_events(current_edited_identifier, Array()); } + } else if (type == "event") { + current_events.remove(current_event_index); - undo_redo->create_action(TTR("Erase Shortcut")); - undo_redo->add_do_method(sc.ptr(), "set_event", Ref<InputEvent>()); - undo_redo->add_undo_method(sc.ptr(), "set_event", sc->get_event()); - undo_redo->add_do_method(this, "_update_shortcuts"); - undo_redo->add_undo_method(this, "_update_shortcuts"); - undo_redo->add_do_method(this, "_settings_changed"); - undo_redo->add_undo_method(this, "_settings_changed"); - undo_redo->commit_action(); - } break; - case EditorSettingsDialog::SHORTCUT_REVERT: { - if (!sc.is_valid()) { - return; //pointless, there is nothing + if (is_editing_action) { + _update_builtin_action(current_edited_identifier, current_events); + } else { + _update_shortcut_events(current_edited_identifier, current_events); } + } + } break; + case EditorSettingsDialog::SHORTCUT_REVERT: { + // Only for "shortcut" types + if (is_editing_action) { + List<Ref<InputEvent>> defaults = InputMap::get_singleton()->get_builtins_with_feature_overrides_applied()[current_edited_identifier]; + Array events = _event_list_to_array_helper(defaults); - Ref<InputEvent> original = sc->get_meta("original"); - - undo_redo->create_action(TTR("Restore Shortcut")); - undo_redo->add_do_method(sc.ptr(), "set_event", original); - undo_redo->add_undo_method(sc.ptr(), "set_event", sc->get_event()); - undo_redo->add_do_method(this, "_update_shortcuts"); - undo_redo->add_undo_method(this, "_update_shortcuts"); - undo_redo->add_do_method(this, "_settings_changed"); - undo_redo->add_undo_method(this, "_settings_changed"); - undo_redo->commit_action(); - } break; - default: - break; - } - } -} - -void EditorSettingsDialog::_builtin_action_popup_index_pressed(int p_index) { - switch (button_idx) { - case SHORTCUT_EDIT: { - if (p_index == action_popup->get_item_count() - 1) { - // Selected last item in list (Add button), therefore add new - current_action_event_index = -1; - shortcut_editor->popup_and_configure(); + _update_builtin_action(current_edited_identifier, events); } else { - // Configure existing - current_action_event_index = p_index; - shortcut_editor->popup_and_configure(action_popup->get_item_metadata(p_index)); + Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(current_edited_identifier); + Array original = sc->get_meta("original"); + _update_shortcut_events(current_edited_identifier, original); } } break; - case SHORTCUT_ERASE: { - current_action_events.remove(p_index); - _update_builtin_action(current_action, current_action_events); - } break; default: break; } } +Variant EditorSettingsDialog::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + TreeItem *selected = shortcuts->get_selected(); + + // Only allow drag for events + if (!selected || !selected->has_meta("type") || selected->get_meta("type") != "event") { + return Variant(); + } + + String label_text = "Event " + itos(selected->get_meta("event_index")); + Label *label = memnew(Label(label_text)); + label->set_modulate(Color(1, 1, 1, 1.0f)); + shortcuts->set_drag_preview(label); + + shortcuts->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN); + + return Dictionary(); // No data required +} + +bool EditorSettingsDialog::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + TreeItem *selected = shortcuts->get_selected(); + TreeItem *item = shortcuts->get_item_at_position(p_point); + if (!selected || !item || item == selected || !item->has_meta("type") || item->get_meta("type") != "event") { + return false; + } + + // Don't allow moving an events in-between shortcuts. + if (selected->get_parent()->get_meta("shortcut_identifier") != item->get_parent()->get_meta("shortcut_identifier")) { + return false; + } + + return true; +} + +void EditorSettingsDialog::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!can_drop_data_fw(p_point, p_data, p_from)) { + return; + } + + TreeItem *selected = shortcuts->get_selected(); + TreeItem *target = shortcuts->get_item_at_position(p_point); + + if (!target) { + return; + } + + int target_event_index = target->get_meta("event_index"); + int index_moving_from = selected->get_meta("event_index"); + + Array events = selected->get_parent()->get_meta("events"); + + Variant event_moved = events[index_moving_from]; + events.remove(index_moving_from); + events.insert(target_event_index, event_moved); + + String ident = selected->get_parent()->get_meta("shortcut_identifier"); + if (selected->get_meta("is_action")) { + _update_builtin_action(ident, events); + } else { + _update_shortcut_events(ident, events); + } +} + void EditorSettingsDialog::_tabs_tab_changed(int p_tab) { _focus_current_search_box(); } @@ -544,13 +610,13 @@ void EditorSettingsDialog::_editor_restart_close() { void EditorSettingsDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_shortcuts"), &EditorSettingsDialog::_update_shortcuts); ClassDB::bind_method(D_METHOD("_settings_changed"), &EditorSettingsDialog::_settings_changed); + + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &EditorSettingsDialog::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &EditorSettingsDialog::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &EditorSettingsDialog::drop_data_fw); } EditorSettingsDialog::EditorSettingsDialog() { - action_popup = memnew(PopupMenu); - action_popup->connect("index_pressed", callable_mp(this, &EditorSettingsDialog::_builtin_action_popup_index_pressed)); - add_child(action_popup); - set_title(TTR("Editor Settings")); undo_redo = memnew(UndoRedo); @@ -628,6 +694,8 @@ EditorSettingsDialog::EditorSettingsDialog() { shortcuts->connect("button_pressed", callable_mp(this, &EditorSettingsDialog::_shortcut_button_pressed)); tab_shortcuts->add_child(shortcuts); + shortcuts->set_drag_forwarding(this); + // Adding event dialog shortcut_editor = memnew(InputEventConfigurationDialog); shortcut_editor->connect("confirmed", callable_mp(this, &EditorSettingsDialog::_event_config_confirmed)); diff --git a/editor/settings_config_dialog.h b/editor/settings_config_dialog.h index 2b6c9b3e1d..7317a014b2 100644 --- a/editor/settings_config_dialog.h +++ b/editor/settings_config_dialog.h @@ -53,29 +53,28 @@ class EditorSettingsDialog : public AcceptDialog { LineEdit *shortcut_search_box; SectionedInspector *inspector; + // Shortcuts enum ShortcutButton { + SHORTCUT_ADD, SHORTCUT_EDIT, SHORTCUT_ERASE, SHORTCUT_REVERT }; - int button_idx; - int current_action_event_index = -1; - bool editing_action = false; - String current_action; - Array current_action_events; - PopupMenu *action_popup; + Tree *shortcuts; + String shortcut_filter; + + InputEventConfigurationDialog *shortcut_editor; + + bool is_editing_action = false; + String current_edited_identifier; + Array current_events; + int current_event_index = -1; Timer *timer; UndoRedo *undo_redo; - // Shortcuts - String shortcut_filter; - Tree *shortcuts; - InputEventConfigurationDialog *shortcut_editor; - String shortcut_being_edited; - virtual void cancel_pressed() override; virtual void ok_pressed() override; @@ -89,7 +88,14 @@ class EditorSettingsDialog : public AcceptDialog { void _event_config_confirmed(); + void _create_shortcut_treeitem(TreeItem *p_parent, const String &p_shortcut_identifier, const String &p_display, Array &p_events, bool p_allow_revert, bool p_is_common, bool p_is_collapsed); + Array _event_list_to_array_helper(List<Ref<InputEvent>> &p_events); void _update_builtin_action(const String &p_name, const Array &p_events); + void _update_shortcut_events(const String &p_path, const Array &p_events); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); void _tabs_tab_changed(int p_tab); void _focus_current_search_box(); |