diff options
Diffstat (limited to 'editor')
78 files changed, 2633 insertions, 1190 deletions
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 6728f60e06..9194da654c 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -929,13 +929,6 @@ void AnimationBezierTrackEdit::_gui_input(const Ref<InputEvent> &p_event) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1); } - // 6-(undo) reinsert overlapped keys - for (List<AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { - - AnimMoveRestore &amr = E->get(); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1); - } - undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index a163490cfb..fa773b17c2 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2466,6 +2466,7 @@ void AnimationTrackEdit::update_play_position() { void AnimationTrackEdit::set_root(Node *p_root) { root = p_root; } + void AnimationTrackEdit::_zoom_changed() { update(); play_position->update(); @@ -3305,6 +3306,7 @@ Ref<Animation> AnimationTrackEditor::get_current_animation() const { return animation; } + void AnimationTrackEditor::_root_removed(Node *p_root) { root = NULL; } @@ -3400,15 +3402,14 @@ void AnimationTrackEditor::_track_remove_request(int p_track) { int idx = p_track; if (idx >= 0 && idx < animation->get_track_count()) { - selection.clear(); - _clear_key_edit(); - //all will be updated after remove anyway, and triggering update here raises error on tracks already removed undo_redo->create_action(TTR("Remove Anim Track")); + undo_redo->add_do_method(this, "_clear_selection", false); undo_redo->add_do_method(animation.ptr(), "remove_track", idx); undo_redo->add_undo_method(animation.ptr(), "add_track", animation->track_get_type(idx), idx); undo_redo->add_undo_method(animation.ptr(), "track_set_path", idx, animation->track_get_path(idx)); - //todo interpolation + + // TODO interpolation. for (int i = 0; i < animation->track_get_key_count(idx); i++) { Variant v = animation->track_get_key_value(idx, i); @@ -3469,20 +3470,18 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) { if (p_id.track_idx == -1) { if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) { //potential new key, does not exist - if (insert_data.size() == 1) - 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?"), insert_data.size())); - + 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) { + 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) { + if (insert_data[i].track_idx == -1) + ++num_tracks; + + if (insert_data[i].type != Animation::TYPE_VALUE) continue; - } + switch (insert_data[i].value.get_type()) { case Variant::INT: case Variant::REAL: @@ -3491,7 +3490,7 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) { case Variant::QUAT: case Variant::PLANE: case Variant::COLOR: { - //good + // Valid. } break; default: { all_bezier = false; @@ -3499,6 +3498,11 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) { } } + if (num_tracks == 1) + 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)); + insert_confirm_bezier->set_visible(all_bezier); insert_confirm->get_ok()->set_text(TTR("Create")); insert_confirm->popup_centered_minsize(); @@ -3685,16 +3689,20 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { Variant value; - if (animation->track_get_path(i) == np) { + String track_path = animation->track_get_path(i); + if (track_path == np) { value = p_value; //all good } else { - String tpath = animation->track_get_path(i); - if (NodePath(tpath.get_basename()) == np) { - String subindex = tpath.get_extension(); - value = p_value.get(subindex); - } else { + int sep = track_path.find_last(":"); + if (sep != -1) { + String base_path = track_path.substr(0, sep); + if (base_path == np) { + String value_name = track_path.substr(sep + 1); + value = p_value.get(value_name); + } else + continue; + } else continue; - } } InsertData id; @@ -3954,33 +3962,30 @@ int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, boo bool created = false; if (p_id.track_idx < 0) { - if (p_create_beziers && (p_id.value.get_type() == Variant::VECTOR2 || - p_id.value.get_type() == Variant::VECTOR3 || - p_id.value.get_type() == Variant::QUAT || - p_id.value.get_type() == Variant::COLOR || - p_id.value.get_type() == Variant::PLANE)) { - - Vector<String> subindices = _get_bezier_subindices_for_type(p_id.value.get_type()); + if (p_create_beziers) { + bool valid; + Vector<String> subindices = _get_bezier_subindices_for_type(p_id.value.get_type(), &valid); + if (valid) { + for (int i = 0; i < subindices.size(); i++) { + InsertData id = p_id; + id.type = Animation::TYPE_BEZIER; + id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length())); + id.path = String(p_id.path) + subindices[i]; + _confirm_insert(id, p_last_track + i); + } - for (int i = 0; i < subindices.size(); i++) { - InsertData id = p_id; - id.type = Animation::TYPE_BEZIER; - id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length())); - id.path = String(p_id.path) + subindices[i]; - _confirm_insert(id, p_last_track + i); + return p_last_track + subindices.size(); } - - return p_last_track + subindices.size() - 1; } created = true; undo_redo->create_action(TTR("Anim Insert Track & Key")); Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE; if (p_id.type == Animation::TYPE_VALUE || p_id.type == Animation::TYPE_BEZIER) { - //wants a new tack + // Wants a new track. { - //hack + // Hack. NodePath np; animation->add_track(p_id.type); animation->track_set_path(animation->get_track_count() - 1, p_id.path); @@ -4034,7 +4039,7 @@ int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, boo Dictionary d; d["location"] = tr.origin; d["scale"] = tr.basis.get_scale(); - d["rotation"] = Quat(tr.basis); //.orthonormalized(); + d["rotation"] = Quat(tr.basis); value = d; } break; case Animation::TYPE_BEZIER: { @@ -4059,8 +4064,9 @@ int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, boo if (created) { - //just remove the track - undo_redo->add_undo_method(animation.ptr(), "remove_track", p_last_track); + // Just remove the track. + undo_redo->add_undo_method(this, "_clear_selection", false); + undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); p_last_track++; } else { @@ -4079,6 +4085,8 @@ int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, boo } void AnimationTrackEditor::show_select_node_warning(bool p_show) { + + info_message->set_visible(p_show); } bool AnimationTrackEditor::is_key_selected(int p_track, int p_key) const { @@ -4584,7 +4592,7 @@ void AnimationTrackEditor::_new_track_property_selected(String p_name) { for (int i = 0; i < subindices.size(); i++) { undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type); undo_redo->add_do_method(animation.ptr(), "track_set_path", base_track + i, full_path + subindices[i]); - undo_redo->add_undo_method(animation.ptr(), "remove_track", base_track + i); + undo_redo->add_undo_method(animation.ptr(), "remove_track", base_track); } undo_redo->commit_action(); } @@ -4661,6 +4669,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { undo_redo->create_action(TTR("Add Track Key")); undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, value); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); undo_redo->commit_action(); @@ -4868,11 +4877,16 @@ void AnimationTrackEditor::_clear_key_edit() { } } -void AnimationTrackEditor::_clear_selection() { +void AnimationTrackEditor::_clear_selection(bool p_update) { + selection.clear(); - for (int i = 0; i < track_edits.size(); i++) { - track_edits[i]->update(); + + if (p_update) { + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } } + _clear_key_edit(); } @@ -5543,7 +5557,6 @@ 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); undo_redo->commit_action(); - //selection.clear(); _update_key_edit(); } } break; @@ -5794,6 +5807,14 @@ AnimationTrackEditor::AnimationTrackEditor() { timeline_vbox->set_h_size_flags(SIZE_EXPAND_FILL); timeline_vbox->add_constant_override("separation", 0); + info_message = memnew(Label); + info_message->set_text(TTR("Select an AnimationPlayer node to create and edit animations.")); + info_message->set_valign(Label::VALIGN_CENTER); + info_message->set_align(Label::ALIGN_CENTER); + info_message->set_autowrap(true); + info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); + main_panel->add_child(info_message); + timeline = memnew(AnimationTimelineEdit); timeline->set_undo_redo(undo_redo); timeline_vbox->add_child(timeline); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 96fd10effd..830d5b52d3 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -304,6 +304,8 @@ class AnimationTrackEditor : public VBoxContainer { VBoxContainer *track_vbox; AnimationBezierTrackEdit *bezier_edit; + Label *info_message; + AnimationTimelineEdit *timeline; HSlider *zoom; EditorSpinSlider *step; @@ -385,7 +387,7 @@ class AnimationTrackEditor : public VBoxContainer { void _insert_key_from_track(float p_ofs, int p_track); void _add_method_key(const String &p_method); - void _clear_selection(); + void _clear_selection(bool p_update = false); void _clear_selection_for_anim(const Ref<Animation> &p_anim); void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos); diff --git a/editor/collada/collada.cpp b/editor/collada/collada.cpp index 1bb49a4167..e38171eef9 100644 --- a/editor/collada/collada.cpp +++ b/editor/collada/collada.cpp @@ -616,7 +616,7 @@ void Collada::_parse_effect_material(XMLParser &parser, Effect &effect, String & if (colorarr.size() >= 3) { - // alpha strangely not allright? maybe it needs to be multiplied by value as a channel intensity + // alpha strangely not alright? maybe it needs to be multiplied by value as a channel intensity Color color(colorarr[0], colorarr[1], colorarr[2], 1.0); if (what == "diffuse") effect.diffuse.color = color; @@ -854,7 +854,7 @@ void Collada::_parse_light(XMLParser &parser) { COLLADA_PRINT("colorarr size: " + rtos(colorarr.size())); if (colorarr.size() >= 4) { - // alpha strangely not allright? maybe it needs to be multiplied by value as a channel intensity + // alpha strangely not alright? maybe it needs to be multiplied by value as a channel intensity Color color(colorarr[0], colorarr[1], colorarr[2], 1.0); light.color = color; } @@ -2297,7 +2297,7 @@ bool Collada::_optimize_skeletons(VisualScene *p_vscene, Node *p_node) { //replace parent by this... Node *parent = node->parent; - //i wonder if this is allright.. i think it is since created skeleton (first joint) is already animated by bone.. + //i wonder if this is alright.. i think it is since created skeleton (first joint) is already animated by bone.. node->id = parent->id; node->name = parent->name; node->xform_list = parent->xform_list; diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index c5b81c4685..cfc2ec11cf 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -529,7 +529,7 @@ void ConnectionsDock::_make_or_edit_connection() { // Pick up args here before "it" is deleted by update_tree. script_function_args = it->get_metadata(0).operator Dictionary()["args"]; for (int i = 0; i < cToMake.binds.size(); i++) { - script_function_args.append("extra_arg_" + itos(i)); + script_function_args.append("extra_arg_" + itos(i) + ":" + Variant::get_type_name(cToMake.binds[i].get_type())); } } diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index 2180742bbb..31095b1100 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -31,6 +31,7 @@ #include "editor_audio_buses.h" #include "core/io/resource_saver.h" +#include "core/os/input.h" #include "core/os/keyboard.h" #include "editor_node.h" #include "filesystem_dock.h" @@ -321,7 +322,13 @@ void EditorAudioBus::_volume_changed(float p_normalized) { updating_bus = true; - float p_db = this->_normalized_volume_to_scaled_db(p_normalized); + const float p_db = this->_normalized_volume_to_scaled_db(p_normalized); + + if (Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + // Snap the value when holding Ctrl for easier editing. + // To do so, it needs to be converted back to normalized volume (as the slider uses that unit). + slider->set_value(_scaled_db_to_normalized_volume(Math::round(p_db))); + } UndoRedo *ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Change Audio Bus Volume"), UndoRedo::MERGE_ENDS); @@ -376,14 +383,24 @@ float EditorAudioBus::_scaled_db_to_normalized_volume(float db) { } void EditorAudioBus::_show_value(float slider_value) { - String text = vformat("%10.1f dB", _normalized_volume_to_scaled_db(slider_value)); + float db; + if (Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + // Display the correct (snapped) value when holding Ctrl + db = Math::round(_normalized_volume_to_scaled_db(slider_value)); + } else { + db = _normalized_volume_to_scaled_db(slider_value); + } + + String text = vformat("%10.1f dB", db); + + slider->set_tooltip(text); audio_value_preview_label->set_text(text); Vector2 slider_size = slider->get_size(); Vector2 slider_position = slider->get_global_position(); float left_padding = 5.0f; float vert_padding = 10.0f; - Vector2 box_position = Vector2(slider_size.x + left_padding, (slider_size.y - vert_padding) * (1.0f - slider_value) - vert_padding); + Vector2 box_position = Vector2(slider_size.x + left_padding, (slider_size.y - vert_padding) * (1.0f - slider->get_value()) - vert_padding); audio_value_preview_box->set_position(slider_position + box_position); audio_value_preview_box->set_size(audio_value_preview_label->get_size()); if (slider->has_focus() && !audio_value_preview_box->is_visible()) { @@ -773,7 +790,7 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { is_master = p_is_master; hovering_drop = false; - set_tooltip(TTR("Audio Bus, Drag and Drop to rearrange.")); + set_tooltip(TTR("Drag & drop to rearrange.")); VBoxContainer *vb = memnew(VBoxContainer); add_child(vb); @@ -1439,7 +1456,7 @@ Size2 EditorAudioMeterNotches::get_minimum_size() const { float width = 0; float height = top_padding + btm_padding; - for (uint8_t i = 0; i < notches.size(); i++) { + for (int i = 0; i < notches.size(); i++) { if (notches[i].render_db_value) { width = MAX(width, font->get_string_size(String::num(Math::abs(notches[i].db_value)) + "dB").x); height += font_height; @@ -1473,7 +1490,7 @@ void EditorAudioMeterNotches::_draw_audio_notches() { Ref<Font> font = get_font("font", "Label"); float font_height = font->get_height(); - for (uint8_t i = 0; i < notches.size(); i++) { + for (int i = 0; i < notches.size(); i++) { AudioNotch n = notches[i]; draw_line(Vector2(0, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding), Vector2(line_length, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding), diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index e6df00b48c..04e9177d26 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -30,6 +30,7 @@ #include "editor_help.h" +#include "core/os/input.h" #include "core/os/keyboard.h" #include "doc_data_compressed.gen.h" #include "editor/plugins/script_editor_plugin.h" @@ -257,16 +258,17 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview } class_desc->push_color(symbol_color); - class_desc->add_text(p_method.arguments.size() || is_vararg ? "( " : "("); + class_desc->add_text("("); class_desc->pop(); for (int j = 0; j < p_method.arguments.size(); j++) { class_desc->push_color(text_color); if (j > 0) class_desc->add_text(", "); - _add_type(p_method.arguments[j].type, p_method.arguments[j].enumeration); - class_desc->add_text(" "); + _add_text(p_method.arguments[j].name); + class_desc->add_text(": "); + _add_type(p_method.arguments[j].type, p_method.arguments[j].enumeration); if (p_method.arguments[j].default_value != "") { class_desc->push_color(symbol_color); @@ -291,7 +293,7 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview } class_desc->push_color(symbol_color); - class_desc->add_text(p_method.arguments.size() || is_vararg ? " )" : ")"); + class_desc->add_text(")"); class_desc->pop(); if (p_method.qualifiers != "") { @@ -424,7 +426,7 @@ void EditorHelp::_update_doc() { class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Brief Description:")); + class_desc->add_text(TTR("Brief Description")); class_desc->pop(); class_desc->pop(); @@ -451,7 +453,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Properties"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Properties:")); + class_desc->add_text(TTR("Properties")); class_desc->pop(); class_desc->pop(); @@ -547,7 +549,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Methods"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Methods:")); + class_desc->add_text(TTR("Methods")); class_desc->pop(); class_desc->pop(); @@ -618,7 +620,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Theme Properties"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Theme Properties:")); + class_desc->add_text(TTR("Theme Properties")); class_desc->pop(); class_desc->pop(); @@ -685,7 +687,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Signals"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Signals:")); + class_desc->add_text(TTR("Signals")); class_desc->pop(); class_desc->pop(); @@ -702,15 +704,16 @@ void EditorHelp::_update_doc() { _add_text(cd.signals[i].name); class_desc->pop(); class_desc->push_color(symbol_color); - class_desc->add_text(cd.signals[i].arguments.size() ? "( " : "("); + class_desc->add_text("("); class_desc->pop(); for (int j = 0; j < cd.signals[i].arguments.size(); j++) { class_desc->push_color(text_color); if (j > 0) class_desc->add_text(", "); - _add_type(cd.signals[i].arguments[j].type); - class_desc->add_text(" "); + _add_text(cd.signals[i].arguments[j].name); + class_desc->add_text(": "); + _add_type(cd.signals[i].arguments[j].type); if (cd.signals[i].arguments[j].default_value != "") { class_desc->push_color(symbol_color); @@ -723,7 +726,7 @@ void EditorHelp::_update_doc() { } class_desc->push_color(symbol_color); - class_desc->add_text(cd.signals[i].arguments.size() ? " )" : ")"); + class_desc->add_text(")"); class_desc->pop(); class_desc->pop(); // end monofont if (cd.signals[i].description != "") { @@ -770,7 +773,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Enumerations"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Enumerations:")); + class_desc->add_text(TTR("Enumerations")); class_desc->pop(); class_desc->pop(); class_desc->push_indent(1); @@ -856,7 +859,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Constants"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Constants:")); + class_desc->add_text(TTR("Constants")); class_desc->pop(); class_desc->pop(); class_desc->push_indent(1); @@ -916,7 +919,7 @@ void EditorHelp::_update_doc() { description_line = class_desc->get_line_count() - 2; class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Class Description:")); + class_desc->add_text(TTR("Class Description")); class_desc->pop(); class_desc->pop(); @@ -938,7 +941,7 @@ void EditorHelp::_update_doc() { { class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Online Tutorials:")); + class_desc->add_text(TTR("Online Tutorials")); class_desc->pop(); class_desc->pop(); class_desc->push_indent(1); @@ -980,7 +983,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Property Descriptions"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Property Descriptions:")); + class_desc->add_text(TTR("Property Descriptions")); class_desc->pop(); class_desc->pop(); @@ -1090,7 +1093,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Method Descriptions"), class_desc->get_line_count() - 2)); class_desc->push_color(title_color); class_desc->push_font(doc_title_font); - class_desc->add_text(TTR("Method Descriptions:")); + class_desc->add_text(TTR("Method Descriptions")); class_desc->pop(); class_desc->pop(); @@ -1807,5 +1810,9 @@ void FindBar::_search_text_changed(const String &p_text) { void FindBar::_search_text_entered(const String &p_text) { - search_next(); + if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + search_prev(); + } else { + search_next(); + } } diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index af79c21f85..27e61362ed 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -338,10 +338,15 @@ bool EditorHelpSearch::Runner::_phase_match_classes() { if (search_flags & SEARCH_METHODS) for (int i = 0; i < class_doc.methods.size(); i++) { String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.methods[i].name : class_doc.methods[i].name.to_lower(); - if (method_name.find(term) > -1 || - (term.begins_with(".") && method_name.begins_with(term.right(1))) || - (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || - (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) + String aux_term = (search_flags & SEARCH_CASE_SENSITIVE) ? term : term.to_lower(); + + if (aux_term.begins_with(".")) + aux_term = aux_term.right(1); + + if (aux_term.ends_with("(")) + aux_term = aux_term.left(aux_term.length() - 1).strip_edges(); + + if (aux_term.is_subsequence_of(method_name)) match.methods.push_back(const_cast<DocData::MethodDoc *>(&class_doc.methods[i])); } if (search_flags & SEARCH_SIGNALS) @@ -431,9 +436,9 @@ bool EditorHelpSearch::Runner::_phase_select_match() { bool EditorHelpSearch::Runner::_match_string(const String &p_term, const String &p_string) const { if (search_flags & SEARCH_CASE_SENSITIVE) - return p_string.find(p_term) > -1; + return p_term.is_subsequence_of(p_string); else - return p_string.findn(p_term) > -1; + return p_term.is_subsequence_ofi(p_string); } void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_text) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index a76d34e122..8d5858a10d 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -1046,9 +1046,9 @@ void EditorInspectorSection::_notification(int p_what) { if (foldable) { if (object->editor_is_section_unfolded(section)) { - arrow = get_icon("arrow_up", "Tree"); - } else { arrow = get_icon("arrow", "Tree"); + } else { + arrow = get_icon("arrow_collapsed", "Tree"); } } @@ -1087,9 +1087,9 @@ void EditorInspectorSection::_notification(int p_what) { if (foldable) { if (object->editor_is_section_unfolded(section)) { - arrow = get_icon("arrow_up", "Tree"); - } else { arrow = get_icon("arrow", "Tree"); + } else { + arrow = get_icon("arrow_collapsed", "Tree"); } } @@ -1103,13 +1103,12 @@ void EditorInspectorSection::_notification(int p_what) { draw_rect(Rect2(Vector2(), Vector2(get_size().width, h)), bg_color); - int hs = get_constant("hseparation", "Tree"); - + const int arrow_margin = 3; Color color = get_color("font_color", "Tree"); - draw_string(font, Point2(hs, font->get_ascent() + (h - font->get_height()) / 2).floor(), label, color, get_size().width); + draw_string(font, Point2(Math::round((16 + arrow_margin) * EDSCALE), font->get_ascent() + (h - font->get_height()) / 2).floor(), label, color, get_size().width); if (arrow.is_valid()) { - draw_texture(arrow, Point2(get_size().width - arrow->get_width(), (h - arrow->get_height()) / 2).floor()); + draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor()); } } } diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index b5cdc76115..5474f86c74 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -113,6 +113,10 @@ void EditorLog::add_message(const String &p_msg, MessageType p_type) { log->add_text(" "); tool_button->set_icon(icon); } break; + case MSG_TYPE_EDITOR: { + // Distinguish editor messages from messages printed by the project + log->push_color(get_color("font_color", "Editor") * Color(1, 1, 1, 0.6)); + } break; } log->add_text(p_msg); @@ -128,7 +132,7 @@ void EditorLog::set_tool_button(ToolButton *p_tool_button) { void EditorLog::_undo_redo_cbk(void *p_self, const String &p_name) { EditorLog *self = (EditorLog *)p_self; - self->add_message(p_name); + self->add_message(p_name, EditorLog::MSG_TYPE_EDITOR); } void EditorLog::_bind_methods() { diff --git a/editor/editor_log.h b/editor/editor_log.h index bb56bd34fe..10561b9c83 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -74,7 +74,8 @@ public: enum MessageType { MSG_TYPE_STD, MSG_TYPE_ERROR, - MSG_TYPE_WARNING + MSG_TYPE_WARNING, + MSG_TYPE_EDITOR }; void add_message(const String &p_msg, MessageType p_type = MSG_TYPE_STD); diff --git a/editor/editor_network_profiler.cpp b/editor/editor_network_profiler.cpp new file mode 100644 index 0000000000..8482c4e38a --- /dev/null +++ b/editor/editor_network_profiler.cpp @@ -0,0 +1,211 @@ +/*************************************************************************/ +/* editor_network_profiler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_network_profiler.h" + +#include "core/os/os.h" +#include "editor_scale.h" +#include "editor_settings.h" + +void EditorNetworkProfiler::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_frame"), &EditorNetworkProfiler::_update_frame); + ClassDB::bind_method(D_METHOD("_activate_pressed"), &EditorNetworkProfiler::_activate_pressed); + ClassDB::bind_method(D_METHOD("_clear_pressed"), &EditorNetworkProfiler::_clear_pressed); + ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable"))); +} + +void EditorNetworkProfiler::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + activate->set_icon(get_icon("Play", "EditorIcons")); + clear_button->set_icon(get_icon("Clear", "EditorIcons")); + incoming_bandwidth_text->set_right_icon(get_icon("ArrowDown", "EditorIcons")); + outgoing_bandwidth_text->set_right_icon(get_icon("ArrowUp", "EditorIcons")); + + // This needs to be done here to set the faded color when the profiler is first opened + incoming_bandwidth_text->add_color_override("font_color_uneditable", get_color("font_color", "Editor") * Color(1, 1, 1, 0.5)); + outgoing_bandwidth_text->add_color_override("font_color_uneditable", get_color("font_color", "Editor") * Color(1, 1, 1, 0.5)); + } +} + +void EditorNetworkProfiler::_update_frame() { + + counters_display->clear(); + + TreeItem *root = counters_display->create_item(); + + for (Map<ObjectID, MultiplayerAPI::ProfilingInfo>::Element *E = nodes_data.front(); E; E = E->next()) { + + 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)); + } +} + +void EditorNetworkProfiler::_activate_pressed() { + + if (activate->is_pressed()) { + activate->set_icon(get_icon("Stop", "EditorIcons")); + activate->set_text(TTR("Stop")); + } else { + activate->set_icon(get_icon("Play", "EditorIcons")); + activate->set_text(TTR("Start")); + } + emit_signal("enable_profiling", activate->is_pressed()); +} + +void EditorNetworkProfiler::_clear_pressed() { + nodes_data.clear(); + set_bandwidth(0, 0); + if (frame_delay->is_stopped()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } +} + +void EditorNetworkProfiler::add_node_frame_data(const MultiplayerAPI::ProfilingInfo p_frame) { + + if (!nodes_data.has(p_frame.node)) { + nodes_data.insert(p_frame.node, p_frame); + } else { + nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc; + nodes_data[p_frame.node].incoming_rset += p_frame.incoming_rset; + nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc; + nodes_data[p_frame.node].outgoing_rset += p_frame.outgoing_rset; + } + + if (frame_delay->is_stopped()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } +} + +void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) { + + incoming_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_incoming))); + outgoing_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_outgoing))); + + // Make labels more prominent when the bandwidth is greater than 0 to attract user attention + incoming_bandwidth_text->add_color_override( + "font_color_uneditable", + get_color("font_color", "Editor") * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5)); + outgoing_bandwidth_text->add_color_override( + "font_color_uneditable", + get_color("font_color", "Editor") * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5)); +} + +bool EditorNetworkProfiler::is_profiling() { + return activate->is_pressed(); +} + +EditorNetworkProfiler::EditorNetworkProfiler() { + + HBoxContainer *hb = memnew(HBoxContainer); + hb->add_constant_override("separation", 8 * EDSCALE); + add_child(hb); + + activate = memnew(Button); + activate->set_toggle_mode(true); + activate->set_text(TTR("Start")); + activate->connect("pressed", this, "_activate_pressed"); + hb->add_child(activate); + + clear_button = memnew(Button); + clear_button->set_text(TTR("Clear")); + clear_button->connect("pressed", this, "_clear_pressed"); + hb->add_child(clear_button); + + hb->add_spacer(); + + Label *lb = memnew(Label); + lb->set_text(TTR("Down")); + hb->add_child(lb); + + incoming_bandwidth_text = memnew(LineEdit); + incoming_bandwidth_text->set_editable(false); + incoming_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); + incoming_bandwidth_text->set_align(LineEdit::Align::ALIGN_RIGHT); + hb->add_child(incoming_bandwidth_text); + + Control *down_up_spacer = memnew(Control); + down_up_spacer->set_custom_minimum_size(Size2(30, 0) * EDSCALE); + hb->add_child(down_up_spacer); + + lb = memnew(Label); + lb->set_text(TTR("Up")); + hb->add_child(lb); + + outgoing_bandwidth_text = memnew(LineEdit); + outgoing_bandwidth_text->set_editable(false); + outgoing_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); + outgoing_bandwidth_text->set_align(LineEdit::Align::ALIGN_RIGHT); + hb->add_child(outgoing_bandwidth_text); + + // Set initial texts in the incoming/outgoing bandwidth labels + set_bandwidth(0, 0); + + counters_display = memnew(Tree); + counters_display->set_custom_minimum_size(Size2(300, 0) * EDSCALE); + counters_display->set_v_size_flags(SIZE_EXPAND_FILL); + counters_display->set_hide_folding(true); + counters_display->set_hide_root(true); + counters_display->set_columns(5); + counters_display->set_column_titles_visible(true); + counters_display->set_column_title(0, TTR("Node")); + counters_display->set_column_expand(0, true); + counters_display->set_column_min_width(0, 60 * EDSCALE); + counters_display->set_column_title(1, TTR("Incoming RPC")); + counters_display->set_column_expand(1, false); + counters_display->set_column_min_width(1, 120 * EDSCALE); + counters_display->set_column_title(2, TTR("Incoming RSET")); + counters_display->set_column_expand(2, false); + counters_display->set_column_min_width(2, 120 * EDSCALE); + counters_display->set_column_title(3, TTR("Outgoing RPC")); + counters_display->set_column_expand(3, false); + counters_display->set_column_min_width(3, 120 * EDSCALE); + counters_display->set_column_title(4, TTR("Outgoing RSET")); + counters_display->set_column_expand(4, false); + counters_display->set_column_min_width(4, 120 * EDSCALE); + add_child(counters_display); + + frame_delay = memnew(Timer); + frame_delay->set_wait_time(0.1); + frame_delay->set_one_shot(true); + add_child(frame_delay); + frame_delay->connect("timeout", this, "_update_frame"); +} diff --git a/editor/editor_network_profiler.h b/editor/editor_network_profiler.h new file mode 100644 index 0000000000..85fec340fd --- /dev/null +++ b/editor/editor_network_profiler.h @@ -0,0 +1,72 @@ +/*************************************************************************/ +/* editor_network_profiler.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITORNETWORKPROFILER_H +#define EDITORNETWORKPROFILER_H + +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/split_container.h" +#include "scene/gui/tree.h" + +class EditorNetworkProfiler : public VBoxContainer { + + GDCLASS(EditorNetworkProfiler, VBoxContainer) + +private: + Button *activate; + Button *clear_button; + Tree *counters_display; + LineEdit *incoming_bandwidth_text; + LineEdit *outgoing_bandwidth_text; + + Timer *frame_delay; + + Map<ObjectID, MultiplayerAPI::ProfilingInfo> nodes_data; + + void _update_frame(); + + void _activate_pressed(); + void _clear_pressed(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void add_node_frame_data(const MultiplayerAPI::ProfilingInfo p_frame); + void set_bandwidth(int p_incoming, int p_outgoing); + bool is_profiling(); + + EditorNetworkProfiler(); +}; + +#endif //EDITORNETWORKPROFILER_H diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index a1998a1d7c..1196a26882 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2212,27 +2212,27 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case EDIT_UNDO: { if (Input::get_singleton()->get_mouse_button_mask() & 0x7) { - log->add_message("Can't UNDO while mouse buttons are pressed."); + log->add_message("Can't undo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR); } else { String action = editor_data.get_undo_redo().get_current_action_name(); if (!editor_data.get_undo_redo().undo()) { - log->add_message("There is nothing to UNDO."); + log->add_message("Nothing to undo.", EditorLog::MSG_TYPE_EDITOR); } else if (action != "") { - log->add_message("UNDO: " + action); + log->add_message("Undo: " + action, EditorLog::MSG_TYPE_EDITOR); } } } break; case EDIT_REDO: { if (Input::get_singleton()->get_mouse_button_mask() & 0x7) { - log->add_message("Can't REDO while mouse buttons are pressed."); + log->add_message("Can't redo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR); } else { if (!editor_data.get_undo_redo().redo()) { - log->add_message("There is nothing to REDO."); + log->add_message("Nothing to redo.", EditorLog::MSG_TYPE_EDITOR); } else { String action = editor_data.get_undo_redo().get_current_action_name(); - log->add_message("REDO: " + action); + log->add_message("Redo: " + action, EditorLog::MSG_TYPE_EDITOR); } } } break; @@ -2519,7 +2519,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { bool was_visible = OS::get_singleton()->is_console_visible(); OS::get_singleton()->set_console_visible(!was_visible); - EditorSettings::get_singleton()->set_setting("interface/editor/hide_console_window", !was_visible); + EditorSettings::get_singleton()->set_setting("interface/editor/hide_console_window", was_visible); } break; case EDITOR_SCREENSHOT: { @@ -3133,7 +3133,14 @@ void EditorNode::_clear_undo_history() { void EditorNode::set_current_scene(int p_idx) { + //Save the folding in case the scene gets reloaded. + if (editor_data.get_scene_path(p_idx) != "") + editor_folding.save_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx)); + if (editor_data.check_and_update_scene(p_idx)) { + if (editor_data.get_scene_path(p_idx) != "") + editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx)); + call_deferred("_clear_undo_history"); } diff --git a/editor/editor_path.cpp b/editor/editor_path.cpp index 23d28261d1..f487a3048b 100644 --- a/editor/editor_path.cpp +++ b/editor/editor_path.cpp @@ -74,13 +74,15 @@ void EditorPath::_about_to_show() { objects.clear(); get_popup()->clear(); get_popup()->set_size(Size2(get_size().width, 1)); + _add_children_to_popup(obj); + if (get_popup()->get_item_count() == 0) { + get_popup()->add_item(TTR("No sub-resources found.")); + get_popup()->set_item_disabled(0, true); + } } void EditorPath::update_path() { - set_text(""); - set_tooltip(""); - set_icon(NULL); for (int i = 0; i < history->get_path_size(); i++) { diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 30fb561fbe..f456cf7233 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -922,16 +922,29 @@ EditorPropertyFloat::EditorPropertyFloat() { void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { - Ref<InputEventMouseButton> mb = p_ev; - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { - preset->set_global_position(easing_draw->get_global_transform().xform(mb->get_position())); - preset->popup(); - } - if (mb.is_valid() && mb->is_doubleclick() && mb->get_button_index() == BUTTON_LEFT) { - _setup_spin(); + const Ref<InputEventMouseButton> mb = p_ev; + if (mb.is_valid()) { + if (mb->is_doubleclick() && mb->get_button_index() == BUTTON_LEFT) { + _setup_spin(); + } + + if (mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + preset->set_global_position(easing_draw->get_global_transform().xform(mb->get_position())); + preset->popup(); + + // Ensure the easing doesn't appear as being dragged + dragging = false; + easing_draw->update(); + } + + if (mb->get_button_index() == BUTTON_LEFT) { + dragging = mb->is_pressed(); + // Update to display the correct dragging color + easing_draw->update(); + } } - Ref<InputEventMouseMotion> mm = p_ev; + const Ref<InputEventMouseMotion> mm = p_ev; if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { @@ -969,13 +982,19 @@ void EditorPropertyEasing::_draw_easing() { Rect2 r(Point2(), s); r = r.grow(3); - int points = 48; + const int points = 48; float prev = 1.0; - float exp = get_edited_object()->get(get_edited_property()); + const float exp = get_edited_object()->get(get_edited_property()); - Ref<Font> f = get_font("font", "Label"); - Color color = get_color("font_color", "Label"); + const Ref<Font> f = get_font("font", "Label"); + const Color font_color = get_color("font_color", "Label"); + Color line_color; + if (dragging) { + line_color = get_color("accent_color", "Editor"); + } else { + line_color = get_color("font_color", "Label") * Color(1, 1, 1, 0.9); + } Vector<Point2> lines; for (int i = 1; i <= points; i++) { @@ -983,7 +1002,7 @@ void EditorPropertyEasing::_draw_easing() { float ifl = i / float(points); float iflp = (i - 1) / float(points); - float h = 1.0 - Math::ease(ifl, exp); + const float h = 1.0 - Math::ease(ifl, exp); if (flip) { ifl = 1.0 - ifl; @@ -995,8 +1014,8 @@ void EditorPropertyEasing::_draw_easing() { prev = h; } - easing_draw->draw_multiline(lines, color, 1.0, true); - f->draw(ci, Point2(10, 10 + f->get_ascent()), String::num(exp, 2), color); + easing_draw->draw_multiline(lines, line_color, 1.0, true); + f->draw(ci, Point2(10, 10 + f->get_ascent()), String::num(exp, 2), font_color); } void EditorPropertyEasing::update_property() { @@ -1033,6 +1052,9 @@ void EditorPropertyEasing::_spin_value_changed(double p_value) { void EditorPropertyEasing::_spin_focus_exited() { spin->hide(); + // Ensure the easing doesn't appear as being dragged + dragging = false; + easing_draw->update(); } void EditorPropertyEasing::setup(bool p_full, bool p_flip) { @@ -1095,6 +1117,7 @@ EditorPropertyEasing::EditorPropertyEasing() { spin->hide(); add_child(spin); + dragging = false; flip = false; full = false; } diff --git a/editor/editor_properties.h b/editor/editor_properties.h index adf7779dc4..b8d6aa00c2 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -311,6 +311,7 @@ class EditorPropertyEasing : public EditorProperty { EditorSpinSlider *spin; bool setting; + bool dragging; bool full; bool flip; diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index ff19be8bd5..8abe91bdc1 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -264,7 +264,9 @@ void EditorPropertyArray::update_property() { edit->set_text(String("(Nil) ") + arrtype); edit->set_pressed(false); if (vbox) { + set_bottom_editor(NULL); memdelete(vbox); + vbox = NULL; } return; } @@ -631,7 +633,9 @@ void EditorPropertyDictionary::update_property() { edit->set_text("Dictionary (Nil)"); //This provides symmetry with the array property. edit->set_pressed(false); if (vbox) { + set_bottom_editor(NULL); memdelete(vbox); + vbox = NULL; } return; } diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index 5c15ebb1eb..77e9220b6d 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -116,7 +116,7 @@ void EditorResourcePreview::_preview_ready(const String &p_str, const Ref<Textur uint64_t modified_time = 0; if (p_str.begins_with("ID:")) { - hash = p_str.get_slicec(':', 2).to_int(); + hash = uint32_t(p_str.get_slicec(':', 2).to_int64()); path = "ID:" + p_str.get_slicec(':', 1); } else { modified_time = FileAccess::get_modified_time(path); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index e29e44caa2..32b57e0d65 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -685,12 +685,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // Tree theme->set_icon("checked", "Tree", theme->get_icon("GuiChecked", "EditorIcons")); theme->set_icon("unchecked", "Tree", theme->get_icon("GuiUnchecked", "EditorIcons")); - theme->set_icon("arrow_up", "Tree", theme->get_icon("GuiTreeArrowUp", "EditorIcons")); theme->set_icon("arrow", "Tree", theme->get_icon("GuiTreeArrowDown", "EditorIcons")); theme->set_icon("arrow_collapsed", "Tree", theme->get_icon("GuiTreeArrowRight", "EditorIcons")); theme->set_icon("updown", "Tree", theme->get_icon("GuiTreeUpdown", "EditorIcons")); theme->set_icon("select_arrow", "Tree", theme->get_icon("GuiDropdown", "EditorIcons")); - theme->set_icon("select_option", "Tree", theme->get_icon("GuiTreeOption", "EditorIcons")); theme->set_stylebox("bg_focus", "Tree", style_focus); theme->set_stylebox("custom_button", "Tree", make_empty_stylebox()); theme->set_stylebox("custom_button_pressed", "Tree", make_empty_stylebox()); @@ -704,7 +702,6 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("drop_position_color", "Tree", accent_color); theme->set_constant("vseparation", "Tree", (extra_spacing + default_margin_size) * EDSCALE); theme->set_constant("hseparation", "Tree", (extra_spacing + default_margin_size) * EDSCALE); - theme->set_constant("guide_width", "Tree", border_width); theme->set_constant("item_margin", "Tree", 3 * default_margin_size * EDSCALE); theme->set_constant("button_margin", "Tree", default_margin_size * EDSCALE); theme->set_constant("draw_relationship_lines", "Tree", relationship_line_opacity >= 0.01); @@ -717,6 +714,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { style_tree_btn->set_border_width_all(0); theme->set_stylebox("button_pressed", "Tree", style_tree_btn); + Ref<StyleBoxFlat> style_tree_hover = style_default->duplicate(); + style_tree_hover->set_bg_color(highlight_color * Color(1, 1, 1, 0.4)); + style_tree_hover->set_border_width_all(0); + theme->set_stylebox("hover", "Tree", style_tree_hover); + Ref<StyleBoxFlat> style_tree_focus = style_default->duplicate(); style_tree_focus->set_bg_color(highlight_color); style_tree_focus->set_border_width_all(0); @@ -783,7 +785,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("font_color_fg", "Tabs", font_color); theme->set_color("font_color_bg", "Tabs", font_color_disabled); theme->set_icon("menu", "TabContainer", theme->get_icon("GuiTabMenu", "EditorIcons")); - theme->set_icon("menu_hl", "TabContainer", theme->get_icon("GuiTabMenu", "EditorIcons")); + theme->set_icon("menu_highlight", "TabContainer", theme->get_icon("GuiTabMenuHl", "EditorIcons")); 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")); @@ -793,10 +795,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("decrement", "TabContainer", theme->get_icon("GuiScrollArrowLeft", "EditorIcons")); theme->set_icon("increment", "Tabs", theme->get_icon("GuiScrollArrowRight", "EditorIcons")); theme->set_icon("decrement", "Tabs", theme->get_icon("GuiScrollArrowLeft", "EditorIcons")); - theme->set_icon("increment_highlight", "Tabs", theme->get_icon("GuiScrollArrowRight", "EditorIcons")); - theme->set_icon("decrement_highlight", "Tabs", theme->get_icon("GuiScrollArrowLeft", "EditorIcons")); - theme->set_icon("increment_highlight", "TabContainer", theme->get_icon("GuiScrollArrowRight", "EditorIcons")); - theme->set_icon("decrement_highlight", "TabContainer", theme->get_icon("GuiScrollArrowLeft", "EditorIcons")); + theme->set_icon("increment_highlight", "Tabs", theme->get_icon("GuiScrollArrowRightHl", "EditorIcons")); + theme->set_icon("decrement_highlight", "Tabs", theme->get_icon("GuiScrollArrowLeftHl", "EditorIcons")); + theme->set_icon("increment_highlight", "TabContainer", theme->get_icon("GuiScrollArrowRightHl", "EditorIcons")); + theme->set_icon("decrement_highlight", "TabContainer", theme->get_icon("GuiScrollArrowLeftHl", "EditorIcons")); theme->set_constant("hseparation", "Tabs", 4 * EDSCALE); // Content of each tab diff --git a/editor/editor_vcs_interface.cpp b/editor/editor_vcs_interface.cpp index 8da1777871..4df2a06736 100644 --- a/editor/editor_vcs_interface.cpp +++ b/editor/editor_vcs_interface.cpp @@ -78,18 +78,12 @@ Dictionary EditorVCSInterface::_get_modified_files_data() { } void EditorVCSInterface::_stage_file(String p_file_path) { - - return; } void EditorVCSInterface::_unstage_file(String p_file_path) { - - return; } void EditorVCSInterface::_commit(String p_msg) { - - return; } Array EditorVCSInterface::_get_file_diff(String p_file_path) { @@ -134,7 +128,6 @@ void EditorVCSInterface::stage_file(String p_file_path) { call("_stage_file", p_file_path); } - return; } void EditorVCSInterface::unstage_file(String p_file_path) { @@ -143,7 +136,6 @@ void EditorVCSInterface::unstage_file(String p_file_path) { call("_unstage_file", p_file_path); } - return; } bool EditorVCSInterface::is_addon_ready() { @@ -157,7 +149,6 @@ void EditorVCSInterface::commit(String p_msg) { call("_commit", p_msg); } - return; } Array EditorVCSInterface::get_file_diff(String p_file_path) { diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index 536cfaa1dd..202b554be4 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -562,10 +562,8 @@ bool ExportTemplateManager::can_install_android_template() { Error ExportTemplateManager::install_android_template() { - // To support custom Android builds, we install various things to the project's res://android folder. - // First is the Java source code and buildsystem from android_source.zip. - // Then we extract the Godot Android libraries from pre-build android_release.apk - // and android_debug.apk, to place them in the libs folder. + // To support custom Android builds, we install the Java source code and buildsystem + // from android_source.zip to the project's res://android folder. DirAccessRef da = DirAccess::open("res://"); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); @@ -661,89 +659,6 @@ Error ExportTemplateManager::install_android_template() { ProgressDialog::get_singleton()->end_task("uncompress_src"); unzClose(pkg); - // Extract libs from pre-built APKs. - err = _extract_libs_from_apk("release"); - ERR_FAIL_COND_V_MSG(err != OK, err, "Can't extract Android libs from android_release.apk."); - err = _extract_libs_from_apk("debug"); - ERR_FAIL_COND_V_MSG(err != OK, err, "Can't extract Android libs from android_debug.apk."); - - return OK; -} - -Error ExportTemplateManager::_extract_libs_from_apk(const String &p_target_name) { - - const String &templates_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); - const String &apk_file = templates_path.plus_file("android_" + p_target_name + ".apk"); - ERR_FAIL_COND_V(!FileAccess::exists(apk_file), ERR_CANT_OPEN); - - FileAccess *src_f = NULL; - zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - - unzFile pkg = unzOpen2(apk_file.utf8().get_data(), &io); - ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android APK can't be extracted."); - - DirAccessRef da = DirAccess::open("res://"); - ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - - // 8 steps because 4 arches, 2 libs per arch. - ProgressDialog::get_singleton()->add_task("extract_libs_from_apk", TTR("Extracting Android Libraries From APKs"), 8); - - int ret = unzGoToFirstFile(pkg); - Set<String> dirs_tested; - int idx = 0; - while (ret == UNZ_OK) { - // Get file path. - unz_file_info info; - char fpath[16384]; - ret = unzGetCurrentFileInfo(pkg, &info, fpath, 16384, NULL, 0, NULL, 0); - - String path = fpath; - String base_dir = path.get_base_dir(); - String file = path.get_file(); - - if (!base_dir.begins_with("lib") || path.ends_with("/")) { - ret = unzGoToNextFile(pkg); - continue; - } - - Vector<uint8_t> data; - data.resize(info.uncompressed_size); - - // Read. - unzOpenCurrentFile(pkg); - unzReadCurrentFile(pkg, data.ptrw(), data.size()); - unzCloseCurrentFile(pkg); - - // We have a "lib" folder in the APK, but it should be "libs/{release,debug}" in the source dir. - String target_base_dir = base_dir.replace_first("lib", String("libs").plus_file(p_target_name)); - - if (!dirs_tested.has(base_dir)) { - da->make_dir_recursive(String("android/build").plus_file(target_base_dir)); - dirs_tested.insert(base_dir); - } - - String to_write = String("res://android/build").plus_file(target_base_dir.plus_file(path.get_file())); - FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE); - if (f) { - f->store_buffer(data.ptr(), data.size()); - memdelete(f); -#ifndef WINDOWS_ENABLED - // We can't retrieve Unix permissions from the APK it seems, so simply set 0755 as should be. - FileAccess::set_unix_permissions(to_write, 0755); -#endif - } else { - ERR_PRINTS("Can't uncompress file: " + to_write); - } - - ProgressDialog::get_singleton()->task_step("extract_libs_from_apk", path, idx); - - idx++; - ret = unzGoToNextFile(pkg); - } - - ProgressDialog::get_singleton()->end_task("extract_libs_from_apk"); - unzClose(pkg); - return OK; } diff --git a/editor/export_template_manager.h b/editor/export_template_manager.h index ecb8e85b21..ad3ab507b3 100644 --- a/editor/export_template_manager.h +++ b/editor/export_template_manager.h @@ -72,8 +72,6 @@ class ExportTemplateManager : public ConfirmationDialog { virtual void ok_pressed(); bool _install_from_file(const String &p_file, bool p_use_progress = true); - Error _extract_libs_from_apk(const String &p_target_name); - void _http_download_mirror_completed(int p_status, int p_code, const PoolStringArray &headers, const PoolByteArray &p_data); void _http_download_templates_completed(int p_status, int p_code, const PoolStringArray &headers, const PoolByteArray &p_data); diff --git a/editor/icons/icon_GUI_mini_tab_menu.svg b/editor/icons/icon_GUI_mini_tab_menu.svg deleted file mode 100644 index 8aeb85277c..0000000000 --- a/editor/icons/icon_GUI_mini_tab_menu.svg +++ /dev/null @@ -1,5 +0,0 @@ -<svg width="6" height="16" version="1.1" viewBox="0 0 6 16" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<path transform="translate(0 1036.4)" d="m3 0a2 2 0 0 0 -2 2 2 2 0 0 0 2 2 2 2 0 0 0 2 -2 2 2 0 0 0 -2 -2zm0 6a2 2 0 0 0 -2 2 2 2 0 0 0 2 2 2 2 0 0 0 2 -2 2 2 0 0 0 -2 -2zm0 6a2 2 0 0 0 -2 2 2 2 0 0 0 2 2 2 2 0 0 0 2 -2 2 2 0 0 0 -2 -2z" fill="#fff" fill-opacity=".39216"/> -</g> -</svg> diff --git a/editor/icons/icon_GUI_scroll_arrow_left_hl.svg b/editor/icons/icon_GUI_scroll_arrow_left_hl.svg new file mode 100644 index 0000000000..c4ce1c4432 --- /dev/null +++ b/editor/icons/icon_GUI_scroll_arrow_left_hl.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m8 2a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm1.0137 2a1 1 0 0 0-0.7207 0.29297l-3 3a1.0001 1.0001 0 0 0 0 1.4141l3 3a1 1 0 0 0 1.4141 0 1 1 0 0 0 0-1.4141l-2.293-2.293 2.293-2.293a1 1 0 0 0 0-1.4141 1 1 0 0 0-0.69336-0.29297z" fill="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/> +</svg> diff --git a/editor/icons/icon_GUI_scroll_arrow_right_hl.svg b/editor/icons/icon_GUI_scroll_arrow_right_hl.svg new file mode 100644 index 0000000000..0727b684eb --- /dev/null +++ b/editor/icons/icon_GUI_scroll_arrow_right_hl.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m8 2a6 6 0 0 0-6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0-6-6zm-1.0137 2a1 1 0 0 1 0.7207 0.29297l3 3a1.0001 1.0001 0 0 1 0 1.4141l-3 3a1 1 0 0 1-1.4141 0 1 1 0 0 1 0-1.4141l2.293-2.293-2.293-2.293a1 1 0 0 1 0-1.4141 1 1 0 0 1 0.69336-0.29297z" fill="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/> +</svg> diff --git a/editor/icons/icon_GUI_tab_menu_hl.svg b/editor/icons/icon_GUI_tab_menu_hl.svg new file mode 100644 index 0000000000..e7be7c9154 --- /dev/null +++ b/editor/icons/icon_GUI_tab_menu_hl.svg @@ -0,0 +1,5 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(0 -1036.4)" fill="#e0e0e0"> +<path transform="translate(0 1036.4)" d="m8 0a2 2 0 0 0-2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2zm0 6a2 2 0 0 0-2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2zm0 6a2 2 0 0 0-2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2z" fill="#e0e0e0"/> +</g> +</svg> diff --git a/editor/icons/icon_GUI_tree_option.svg b/editor/icons/icon_GUI_tree_option.svg deleted file mode 100644 index 4200745a78..0000000000 --- a/editor/icons/icon_GUI_tree_option.svg +++ /dev/null @@ -1,5 +0,0 @@ -<svg width="14" height="14" version="1.1" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1038.4)"> -<path transform="translate(0 1038.4)" d="m7.5 1a1.5 1.5 0 0 0 -1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5 -1.5 1.5 1.5 0 0 0 -1.5 -1.5zm0 5a1.5 1.5 0 0 0 -1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5 -1.5 1.5 1.5 0 0 0 -1.5 -1.5zm0 5a1.5 1.5 0 0 0 -1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5 -1.5 1.5 1.5 0 0 0 -1.5 -1.5z" fill="#fff" fill-opacity=".58824" stroke-linejoin="round" stroke-opacity=".39216" stroke-width="2"/> -</g> -</svg> diff --git a/editor/icons/icon_arrow_down.svg b/editor/icons/icon_arrow_down.svg new file mode 100644 index 0000000000..49a93e6e28 --- /dev/null +++ b/editor/icons/icon_arrow_down.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 3.002a1 1 0 0 0 -.69336.29102 1 1 0 0 0 0 1.4141l2.293 2.293h-4.5859c-.55228 0-1 .4477-1 1s.44772 1 1 1h4.5859l-2.293 2.293a1 1 0 0 0 0 1.4141 1 1 0 0 0 1.4141 0l4-4a1.0001 1.0001 0 0 0 0-1.4141l-4-4a1 1 0 0 0 -.7207-.29102z" fill="#e0e0e0" fill-opacity=".99608" transform="matrix(0 1 -1 0 16.0021 -.00004)"/></svg>
\ No newline at end of file diff --git a/editor/icons/icon_arrow_up.svg b/editor/icons/icon_arrow_up.svg index 77a20e8c50..9bf19a6a12 100644 --- a/editor/icons/icon_arrow_up.svg +++ b/editor/icons/icon_arrow_up.svg @@ -1,5 +1 @@ -<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<path d="m2.9875 1044.4a1.0001 1.0001 0 0 0 1.7168 0.6972l2.293-2.2929v4.5859a1.0001 1.0001 0 1 0 2 0v-4.5859l2.293 2.2929a1.0001 1.0001 0 1 0 1.4141 -1.414l-3.9141-3.9141a1.0001 1.0001 0 0 0 -1.5859 0 1.0001 1.0001 0 0 0 -0.00391 0.01l-3.9102 3.9102a1.0001 1.0001 0 0 0 -0.30273 0.7168z" color="#000000" color-rendering="auto" dominant-baseline="auto" fill="#e0e0e0" fill-opacity=".99608" image-rendering="auto" shape-rendering="auto" solid-color="#000000" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"/> -</g> -</svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8.00008 1049.4022a1 1 0 0 0 .69336-.291 1 1 0 0 0 0-1.4141l-2.293-2.293h4.5859c.55228 0 1-.4477 1-1s-.44772-1-1-1h-4.5859l2.293-2.293a1 1 0 0 0 0-1.4141 1 1 0 0 0 -1.4141 0l-4 4a1.0001 1.0001 0 0 0 0 1.4141l4 4a1 1 0 0 0 .7207.291z" fill="#e0e0e0" fill-opacity=".99608" transform="matrix(0 1 -1 0 1052.4021 -.00004)"/></svg>
\ No newline at end of file diff --git a/editor/icons/icon_bus_vu_db.svg b/editor/icons/icon_bus_vu_db.svg deleted file mode 100644 index 236e41e1f5..0000000000 --- a/editor/icons/icon_bus_vu_db.svg +++ /dev/null @@ -1,12 +0,0 @@ -<svg width="32" height="128" version="1.1" viewBox="0 0 32 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> -<defs> -<linearGradient id="a" x1="16" x2="16" y2="128" gradientUnits="userSpaceOnUse"> -<stop stop-color="#ff7a7a" offset="0"/> -<stop stop-color="#e1dc7a" offset=".5"/> -<stop stop-color="#66ff9e" offset="1"/> -</linearGradient> -</defs> -<g transform="translate(0 -924.36)"> -<path transform="translate(0 924.36)" d="m1.5 0c-0.831 0-1.5 0.669-1.5 1.5 0 0.831 0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5 0-0.831-0.669-1.5-1.5-1.5h-2zm0 7c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5zm22.5 3.75c-1.5 0-3 1.3056-3 3.25-0.01912 1.3523 2.0191 1.3523 2 0 0-1.0556 0.5-1.25 1-1.25s1 0.19444 1 1.25c0 0.59157-0.35893 1.156-1.1914 1.8633-0.83248 0.70724-2.0616 1.4518-3.3574 2.3008-0.82974 0.54516-0.44398 1.8356 0.54883 1.8359h5c1.3523 0.01912 1.3523-2.0191 0-2h-1.7852c0.28375-0.20667 0.63106-0.39443 0.88867-0.61328 1.0302-0.87519 1.8965-1.9783 1.8965-3.3867 0-1.9444-1.5-3.25-3-3.25zm-7.0293 0.25195c-0.14519 0.0037-0.28782 0.03907-0.41797 0.10352l-2 1c-1.1924 0.59646-0.29787 2.3855 0.89453 1.7891l0.55273-0.27539v5.3809c-0.01913 1.3523 2.0191 1.3523 2 0v-7c-9.16e-4 -0.56314-0.4664-1.0145-1.0293-0.99805zm-15.471 2.998c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5s-0.669-1.5-1.5-1.5h-2zm0 7c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5zm21.5 3c-0.554 0-1 0.446-1 1v1 2h-0.92969c-0.02343-8.24e-4 -0.046882-8.24e-4 -0.070312 0-1.0702 0-2.0626 0.57318-2.5977 1.5-0.5351 0.92681-0.5351 2.0732 0 3 0.5351 0.92681 1.5275 1.5 2.5977 1.5h2c0.554 0 1-0.446 1-1v-8c0-0.554-0.446-1-1-1zm4 0c-0.554 0-1 0.446-1 1v8c0 0.554 0.446 1 1 1h2c1.0702 0 2.0626-0.57319 2.5977-1.5 0.5351-0.92682 0.5351-2.0732 0-3-0.10504-0.18193-0.23173-0.34698-0.36914-0.5 0.1378-0.15331 0.26385-0.31764 0.36914-0.5 0.5351-0.92682 0.5351-2.0732 0-3-0.5351-0.92682-1.5275-1.5-2.5977-1.5h-2zm-14 1c-1.6447 0-3 1.3553-3 3v3c0 1.6447 1.3553 3 3 3s3-1.3553 3-3v-3c0-1.6447-1.3553-3-3-3zm15 1h1c0.35887 0 0.6858 0.18921 0.86523 0.5 0.17944 0.31079 0.17944 0.68921 0 1-0.17943 0.31079-0.50636 0.5-0.86523 0.5h-0.070312-0.92969v-2zm-15 1c0.5713 0 1 0.4287 1 1v3c0 0.5713-0.4287 1-1 1s-1-0.4287-1-1v-3c0-0.5713 0.4287-1 1-1zm-11.5 1c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5s-0.669-1.5-1.5-1.5h-2zm19.5 2h1v2h-0.92969c-0.02343-8.24e-4 -0.046882-8.24e-4 -0.070312 0-0.35887 0-0.6858-0.18921-0.86523-0.5-0.17944-0.31079-0.17944-0.68921 0-1 0.17943-0.31079 0.50636-0.5 0.86523-0.5zm7 0h1c0.35887 0 0.6858 0.18921 0.86523 0.5 0.17944 0.31079 0.17944 0.68921 0 1-0.17943 0.31079-0.50636 0.5-0.86523 0.5h-1v-2zm-26.5 5c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5zm22.5 2.75c-1.5 0-3 1.3056-3 3.25-0.01912 1.3523 2.0191 1.3523 2 0 0-1.0556 0.5-1.25 1-1.25s1 0.19444 1 1.25c0 0.59157-0.35893 1.156-1.1914 1.8633-0.83248 0.70724-2.0616 1.4518-3.3574 2.3008-0.82974 0.54516-0.44398 1.8356 0.54883 1.8359h5c1.3523 0.01913 1.3523-2.0191 0-2h-1.7852c0.28375-0.20667 0.63106-0.39443 0.88867-0.61328 1.0302-0.87519 1.8965-1.9783 1.8965-3.3867 0-1.9444-1.5-3.25-3-3.25zm-7.0293 0.25195c-0.14519 0.0037-0.28782 0.03907-0.41797 0.10352l-2 1c-1.1924 0.59646-0.29787 2.3855 0.89453 1.7891l0.55273-0.27539v5.3809c-0.01913 1.3523 2.0191 1.3523 2 0v-7c-9.16e-4 -0.56314-0.4664-1.0145-1.0293-0.99805zm-15.471 3.998c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5s-0.669-1.5-1.5-1.5h-2zm7.5 1v2h3v-2h-3zm-7 6c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5zm15.986 3.75c-1.5 0-3 1.3056-3 3.25-0.01913 1.3523 2.0191 1.3523 2 0 0-1.0556 0.5-1.25 1-1.25s1 0.19444 1 1.25c0 0.59157-0.35893 1.156-1.1914 1.8633s-2.0616 1.4518-3.3574 2.3008c-0.82974 0.54516-0.44398 1.8356 0.54883 1.8359h5c1.3523 0.01913 1.3523-2.0191 0-2h-1.7871c0.2841-0.20689 0.63273-0.39419 0.89062-0.61328 1.0302-0.87519 1.8965-1.9783 1.8965-3.3867 0-1.9444-1.5-3.25-3-3.25zm7.0469 0.23828c-0.36561-0.0093-0.70715 0.18167-0.89062 0.49805l-3 5c-0.39877 0.66633 0.080888 1.5131 0.85742 1.5137h3v1c-0.01912 1.3523 2.0191 1.3523 2 0v-2c-5.5e-5 -0.55226-0.44774-0.99994-1-1h-2.2324l2.0898-3.4844c0.40768-0.65656-0.05163-1.5077-0.82422-1.5273zm-23.533 3.0117c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5s-0.669-1.5-1.5-1.5h-2zm7.5 2v2h3v-2h-3zm-7.5 5c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5zm24.547 4.9961c-0.12355-0.0037-0.24673 0.01547-0.36328 0.05664 0 0-0.98349 0.3331-1.8906 1.2402-0.90714 0.90717-1.793 2.457-1.793 4.707-6.13e-4 0.07218 0.006604 0.14421 0.021484 0.21484 0.11389 1.5445 1.4072 2.7852 2.9785 2.7852 1.645 0 3-1.355 3-3 0-1.645-1.355-3-3-3-0.01533 0-0.029642 0.003706-0.044922 0.003906 0.084-0.10099 0.16695-0.21188 0.25195-0.29688 0.59286-0.59287 1.1094-0.75781 1.1094-0.75781 1.0726-0.33926 0.85487-1.9171-0.26953-1.9531zm-9.0605 0.005859c-1.0407 0.006928-2.0405 0.55674-2.584 1.498a1 1 0 0 0 0.36523 1.3672 1 1 0 0 0 1.3672 -0.36719c0.24596-0.42602 0.74477-0.6077 1.207-0.43945 0.46226 0.16824 0.728 0.62882 0.64258 1.1133-0.085422 0.48445-0.49245 0.82617-0.98438 0.82617a1 1 0 0 0 -1 1 1 1 0 0 0 1 1c0.49193 0 0.89896 0.34368 0.98438 0.82812 0.085422 0.48446-0.18032 0.94508-0.64258 1.1133-0.46226 0.1683-0.96107-0.015436-1.207-0.44141-0.27644-0.47871-0.88884-0.6423-1.3672-0.36523-0.47752 0.27639-0.64095 0.88733-0.36523 1.3652 0.72462 1.2553 2.2612 1.816 3.623 1.3203 1.3618-0.4956 2.1813-1.9126 1.9297-3.3398-0.1003-0.56884-0.37254-1.0676-0.74023-1.4746 0.37098-0.40777 0.63937-0.91234 0.74023-1.4844 0.25166-1.4272-0.56786-2.8442-1.9297-3.3398-0.34046-0.12392-0.69218-0.182-1.0391-0.17969zm-15.486 1.998c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5s-0.669-1.5-1.5-1.5h-2zm7.5 2v2h3v-2h-3zm16 1c0.56413 0 1 0.43587 1 1s-0.43587 1-1 1-1-0.43587-1-1 0.43587-1 1-1zm-23.5 4c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5zm16.533 3.9883c-0.36561-0.0093-0.70715 0.18167-0.89062 0.49805l-3 5c-0.39877 0.66633 0.080888 1.5131 0.85742 1.5137h3v1c-0.01912 1.3523 2.0191 1.3523 2 0v-2c-5.5e-5 -0.55226-0.44774-0.99994-1-1h-2.2324l2.0898-3.4844c0.40768-0.65656-0.05163-1.5077-0.82422-1.5273zm6.9668 0.011719c-1.645 0-3 1.355-3 3 0 0.769 0.30369 1.4666 0.78711 2-0.48282 0.53332-0.78711 1.2315-0.78711 2 0 1.645 1.355 3 3 3s3-1.355 3-3c0-0.76846-0.30429-1.4667-0.78711-2 0.48342-0.53345 0.78711-1.231 0.78711-2 0-1.645-1.355-3-3-3zm0 2c0.56413 0 1 0.4359 1 1 0 0.5642-0.43587 1-1 1s-1-0.4358-1-1c0-0.5641 0.43587-1 1-1zm-23.5 1c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5s-0.669-1.5-1.5-1.5h-2zm7.5 2v2h3v-2h-3zm16 1c0.56413 0 1 0.4359 1 1 0 0.5642-0.43587 1-1 1s-1-0.4358-1-1c0-0.5641 0.43587-1 1-1zm-23.5 4c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5zm16.447 3.9824c-0.08995 0.0063-0.17865 0.024647-0.26367 0.054687 0 0-0.98349 0.33509-1.8906 1.2422-0.90714 0.9068-1.793 2.457-1.793 4.707-6.13e-4 0.0722 0.006604 0.14421 0.021484 0.21484 0.11389 1.5445 1.4072 2.7852 2.9785 2.7852 1.645 0 3-1.355 3-3 0-1.6451-1.355-3-3-3-0.01533 0-0.029642 0.003706-0.044922 0.003906 0.084-0.10099 0.16695-0.21187 0.25195-0.29688 0.59286-0.5929 1.1094-0.75781 1.1094-0.75781 1.0726-0.33926 0.85487-1.9171-0.26953-1.9531-0.03318-0.0017-0.066429-0.0017-0.099609 0zm7.0527 0.017578c-1.6447 0-3 1.3553-3 3v3c0 1.6447 1.3553 3 3 3s3-1.3553 3-3v-3c0-1.6447-1.3553-3-3-3zm0 2c0.5713 0 1 0.4287 1 1v3c0 0.5713-0.4287 1-1 1s-1-0.4287-1-1v-3c0-0.5713 0.4287-1 1-1zm-23.5 1c-0.831 0-1.5 0.669-1.5 1.5 0 0.831 0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5 0-0.831-0.669-1.5-1.5-1.5h-2zm15.5 1.9863c0.56413 0 1 0.4358 1 1 0 0.5641-0.43587 1-1 1s-1-0.4359-1-1c0-0.5642 0.43587-1 1-1zm-8 0.013672v2h3v-2h-3zm-7.5 5c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5zm22.5 3.75c-1.5 0-3 1.3056-3 3.25-0.01912 1.3523 2.0191 1.3523 2 0 0-1.0555 0.5-1.25 1-1.25s1 0.1945 1 1.25c0 0.5916-0.35893 1.1561-1.1914 1.8633-0.83248 0.7072-2.0616 1.4518-3.3574 2.3008-0.82975 0.54515-0.44398 1.8356 0.54883 1.8359h5c1.3523 0.0191 1.3523-2.0191 0-2h-1.7852c0.28375-0.2066 0.63106-0.39438 0.88867-0.61328 1.0302-0.8751 1.8965-1.9782 1.8965-3.3867 0-1.9444-1.5-3.25-3-3.25zm-10 0.25c-1.3523-0.0191-1.3523 2.0191 0 2h3.3828l-3.2773 6.5527c-0.59596 1.1926 1.1931 2.0871 1.7891 0.89454l4-8c0.33239-0.66495-0.15113-1.4472-0.89453-1.4473h-5zm-12.5 3c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5h2c0.831 0 1.5-0.669 1.5-1.5s-0.669-1.5-1.5-1.5h-2zm7.5 2v2h3v-2h-3zm-7.5 5c-0.831 0-1.5 0.669-1.5 1.5s0.669 1.5 1.5 1.5 1.5-0.669 1.5-1.5-0.669-1.5-1.5-1.5z" fill="url(#a)"/> -</g> -</svg> diff --git a/editor/icons/icon_camera_texture.svg b/editor/icons/icon_camera_texture.svg new file mode 100644 index 0000000000..5629487451 --- /dev/null +++ b/editor/icons/icon_camera_texture.svg @@ -0,0 +1,5 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(0 -1036.4)"> +<path transform="translate(0,1036.4)" d="m2 1c-0.55228 0-1 0.44772-1 1v12c0 0.55228 0.44772 1 1 1h12c0.55228 0 1-0.44772 1-1v-12c0-0.55228-0.44772-1-1-1h-12zm1 2h10v8h-10v-8zm5.8184 1.0039c-0.85534 9.758e-4 -1.5654 0.66069-1.6289 1.5137-0.30036-0.27229-0.69029-0.4234-1.0957-0.42383-0.90315 0-1.6367 0.73162-1.6367 1.6348 9.732e-4 0.69217 0.43922 1.3103 1.0918 1.541v1.1855c0 0.30198 0.24293 0.54492 0.54492 0.54492h3.2695c0.30199 0 0.54492-0.24294 0.54492-0.54492v-0.54492l1.6367 1.0898v-3.2715l-1.6367 1.0918v-0.96484c0.34606-0.30952 0.54406-0.75251 0.54492-1.2168 0-0.90315-0.73162-1.6348-1.6348-1.6348z" fill="#e0e0e0" fill-opacity=".99608"/> +</g> +</svg> diff --git a/editor/icons/icon_clipped_camera.svg b/editor/icons/icon_clipped_camera.svg new file mode 100644 index 0000000000..dd26abc638 --- /dev/null +++ b/editor/icons/icon_clipped_camera.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m6.5 12v4h3v-1h-2v-3zm-1 0h-2c-0.5 0-1 0.5-1 1v2c-0.01829 0.53653 0.5 1 1 1h2v-1h-2v-2h2zm4-12c-1.5691 0.0017903-2.8718 1.2125-2.9883 2.7773-0.55103-0.49952-1.268-0.77655-2.0117-0.77734-1.6569 0-3 1.3431-3 3 0.00179 1.2698 0.80282 2.4009 2 2.8242v2.1758c0 0.554 0.44599 1 1 1h6c0.55401 0 1-0.446 1-1v-1l3 2v-6l-3 2v-1.7695c0.63486-0.56783 0.99842-1.3788 1-2.2305 0-1.6569-1.3431-3-3-3zm1 12v4h1v-1h1c0.55228 0 1-0.44772 1-1v-1c0-0.55228-0.44775-0.99374-1-1h-1zm1 1h1v1h-1z" fill="#fc9c9c"/> +</svg> diff --git a/editor/icons/icon_height_map_shape.svg b/editor/icons/icon_height_map_shape.svg new file mode 100644 index 0000000000..09d129a273 --- /dev/null +++ b/editor/icons/icon_height_map_shape.svg @@ -0,0 +1,12 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<defs> +<linearGradient id="a" x1="8" x2="8" y1="8" y2="11" gradientUnits="userSpaceOnUse"> +<stop stop-color="#68b6ff" offset="0"/> +<stop stop-color="#a2d2ff" offset="1"/> +</linearGradient> +</defs> +<g transform="translate(0,-1)"> +<path transform="translate(0,-1033.4)" d="m1 1044.4 7 3 7-3-7-3z" fill="#a2d2ff" fill-rule="evenodd"/> +<path d="m3 11c1-1 2-2 2-4s1-3 3-3 3 1 3 3 1 3 2 4z" fill="url(#a)"/> +</g> +</svg> diff --git a/editor/icons/icon_interpolated_camera.svg b/editor/icons/icon_interpolated_camera.svg index 7a33c64ca2..24b4832105 100644 --- a/editor/icons/icon_interpolated_camera.svg +++ b/editor/icons/icon_interpolated_camera.svg @@ -1,5 +1,5 @@ <svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <g transform="translate(0 -1036.4)"> -<path transform="translate(0 1036.4)" d="m9 0a3 3 0 0 0 -2.9883 2.7773 3 3 0 0 0 -2.0117 -0.77734 3 3 0 0 0 -3 3 3 3 0 0 0 2 2.8242v2.1758c0 0.554 0.44599 1 1 1h6c0.55401 0 1-0.446 1-1v-1l3 2v-6l-3 2v-1.7695a3 3 0 0 0 1 -2.2305 3 3 0 0 0 -3 -3zm-6 12v4h1v-4h-1zm3 0v4h1v-1h1a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1-1zm5 0a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h1a1 1 0 0 0 1 -1v-2a1 1 0 0 0 -1 -1h-1zm-4 1h1v1h-1v-1zm4 0h1v2h-1v-2z" fill="#fc9c9c"/> +<path transform="translate(0,1036.4)" d="m9.5 4e-5c-1.5691 0.0017903-2.8718 1.2125-2.9883 2.7773-0.55103-0.49952-1.268-0.77655-2.0117-0.77734-1.6569 0-3 1.3431-3 3 0.00179 1.2698 0.80282 2.4009 2 2.8242v2.1758c0 0.554 0.44599 1 1 1h6c0.55401 0 0.9853-0.4462 1-1v-1l3 2v-6l-3 2v-1.7695c0.63486-0.56783 0.99842-1.3788 1-2.2305 0-1.6569-1.3431-3-3-3zm-6 12v4h1v-4zm3 0v4h1v-1h1c0.55228 0 1-0.44772 1-1v-1c0-0.55228-0.44824-1.024-1-1h-1zm5 0c-0.55228 0-1 0.44772-1 1v2c0 0.55228 0.44772 1 1 1h1c0.55228 0 1-0.44772 1-1v-2c0-0.55228-0.44772-1-1-1zm-4 1h1v1h-1zm4 0h1v2h-1z" fill="#fc9c9c"/> </g> </svg> diff --git a/editor/icons/icon_mesh_texture.svg b/editor/icons/icon_mesh_texture.svg new file mode 100644 index 0000000000..a877877c36 --- /dev/null +++ b/editor/icons/icon_mesh_texture.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m3 1c-1.1046 0-2 0.89543-2 2 5.649e-4 0.71397 0.38169 1.3735 1 1.7305v6.541c-0.61771 0.35663-0.99874 1.0152-1 1.7285 0 1.1046 0.89543 2 2 2 0.71397-5.65e-4 1.3735-0.38169 1.7305-1h6.541c0.35663 0.61771 1.0152 0.99874 1.7285 1 1.1046 0 2-0.89543 2-2 1.01e-4 -0.72747-0.39481-1.3976-1.0312-1.75h0.03125v-6.5215c0.61771-0.35663 0.99874-1.0152 1-1.7285 0-1.1046-0.89543-2-2-2-0.71397 5.648e-4 -1.3735 0.38169-1.7305 1h-6.541c-0.35663-0.61771-1.0152-0.99874-1.7285-1zm1.7266 3h0.6875 5.168 0.68945c0.17478 0.30301 0.42598 0.55488 0.72852 0.73047v0.68359 5.1719 0.68555c-0.30301 0.17478-0.55488 0.42598-0.73047 0.72852h-0.68359-5.1719-0.68555c-0.17478-0.30301-0.42598-0.55488-0.72852-0.73047v-0.6875l-0.0039062 0.003907v-5.8574c0.30302-0.17478 0.55488-0.42598 0.73047-0.72852zm4.0859 2.25v0.70117h-0.8125v0.69922h-1.625v0.69922h-0.8125v0.69922h-0.8125v0.70117h1.625 1.625 1.625 1.625v-1.4004h-0.8125v-1.3984h-0.8125v-0.70117h-0.8125z" fill="#e0e0e0" fill-opacity=".99608"/> +</svg> diff --git a/editor/icons/icon_rich_text_effect.svg b/editor/icons/icon_rich_text_effect.svg new file mode 100644 index 0000000000..c2705ad8c4 --- /dev/null +++ b/editor/icons/icon_rich_text_effect.svg @@ -0,0 +1,6 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<g> +<path d="m1 1v2h7v-2zm9 0v2h5v-2zm-9 4v2h11v-2zm0 4v2h4v-2zm6 0v2h1c-0.044949-0.094701-0.088906-0.20229-0.125-0.3418-0.077717-0.30039-0.10439-0.81722 0.16406-1.293 0.081489-0.1441 0.18202-0.26127 0.28906-0.36523zm-6 4v2h8.2812c-0.066517-0.011548-0.1231-0.014758-0.20117-0.037109-0.30195-0.08645-0.76491-0.33245-1.0352-0.80664-0.23366-0.4121-0.24101-0.84933-0.18945-1.1562z" fill="#e0e0e0"/> +<path d="m12.216 8.598a0.53334 3.2001 0 0 0-0.50976 2.2754 3.2001 0.53334 30 0 0-2.2656-0.71484 3.2001 0.53334 30 0 0 1.75 1.6016 0.53334 3.2001 60 0 0-1.7461 1.5996 0.53334 3.2001 60 0 0 2.2578-0.71094 0.53334 3.2001 0 0 0 0.51367 2.3496 0.53334 3.2001 0 0 0 0.51367-2.3516 3.2001 0.53334 30 0 0 2.2539 0.71094 3.2001 0.53334 30 0 0-1.7441-1.5977 0.53334 3.2001 60 0 0 1.748-1.5996 0.53334 3.2001 60 0 0-2.2617 0.71484 0.53334 3.2001 0 0 0-0.50977-2.2773z" fill="#cea4f1" stroke-width="1.0667"/> +</g> +</svg> diff --git a/editor/icons/icon_skeleton_i_k.svg b/editor/icons/icon_skeleton_i_k.svg new file mode 100644 index 0000000000..851023ab4d --- /dev/null +++ b/editor/icons/icon_skeleton_i_k.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m6 2a4 4 0 0 0-4 4 4 4 0 0 0 2 3.4531v3.5469a2 2 0 0 0 1 1.7324 2 2 0 0 0 1 0.26562v0.001953h4v-0.001953a2 2 0 0 0 1-0.26562 2 2 0 0 0 1-1.7324v-3.5469a4 4 0 0 0 2-3.4531 4 4 0 0 0-4-4h-4zm-1 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zm6 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zm-4 2h2v1h-2v-1zm-2 2h1v1h1v-1h1 1v1h1v-1h1v0.86719 3.1328h-1v-1h-1v1h-1-1v-1h-1v1h-1v-3.1309-0.86914z" fill="#e0e0e0"/> +</svg> diff --git a/editor/icons/icon_soft_body.svg b/editor/icons/icon_soft_body.svg index 9930026b61..2c907df847 100644 --- a/editor/icons/icon_soft_body.svg +++ b/editor/icons/icon_soft_body.svg @@ -1,56 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="16" - height="16" - version="1.1" - viewBox="0 0 16 16" - id="svg2" - inkscape:version="0.91 r13725" - sodipodi:docname="icon_soft_body.svg"> - <metadata - id="metadata14"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs12" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1027" - id="namedview10" - showgrid="false" - inkscape:zoom="18.792233" - inkscape:cx="2.8961304" - inkscape:cy="4.3816933" - inkscape:window-x="-8" - inkscape:window-y="-8" - inkscape:window-maximized="1" - inkscape:current-layer="svg2" /> - <path - style="opacity:1;fill:#fc9c9c;fill-opacity:0.99607843;fill-rule:nonzero;stroke:none;stroke-width:1.42799997;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m 2.3447105,1.6091897 c -0.011911,1.9816766 -1.4168958,3.9344766 0,5.9495986 1.4168957,2.0151221 0,6.6693597 0,6.6693597 l 10.9510055,0 c 0,0 1.780829,-4.4523824 0,-6.489075 -1.780829,-2.0366925 -0.183458,-4.119112 0,-6.1298833 z m 1.8894067,0.7549031 7.4390658,0 c -0.431995,1.5996085 -1.62289,4.0426807 0,5.3749802 1.622888,1.3322996 0,5.887932 0,5.887932 l -7.4390658,0 c 0,0 1.3903413,-4.3680495 0,-5.9780743 -1.3903412,-1.6100247 -0.3951213,-3.7149271 0,-5.2848379 z" - id="rect4142" - inkscape:connector-curvature="0" - sodipodi:nodetypes="czcczcccczcczc" /> -</svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 1s-3 5 0 7-1 7-1 7h13s3-6 0-8 1-6 1-6zm2 2h7s-2 3 1 5 0 5 0 5h-7s2-4-1-6 0-4 0-4z" fill="#fc9c9c" fill-opacity=".996078"/></svg>
\ No newline at end of file diff --git a/editor/icons/icon_spring_arm.svg b/editor/icons/icon_spring_arm.svg new file mode 100644 index 0000000000..0700966369 --- /dev/null +++ b/editor/icons/icon_spring_arm.svg @@ -0,0 +1,6 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m8 14 6-6" fill="none" stroke="#fc9c9c" stroke-width="2"/> +<path d="m2 2 7 7" fill="none" stroke="#fc9c9c" stroke-width="2"/> +<path d="m10 9h-6" fill="none" stroke="#fc9c9c" stroke-width="2"/> +<path d="m9 9v-5" fill="none" stroke="#fc9c9c" stroke-width="2"/> +</svg> diff --git a/editor/icons/icon_style_box_line.svg b/editor/icons/icon_style_box_line.svg new file mode 100644 index 0000000000..28f2eec6c0 --- /dev/null +++ b/editor/icons/icon_style_box_line.svg @@ -0,0 +1,11 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(0 -1036.4)"> +<path transform="translate(0 1036.4)" d="m13.303 1c-0.4344 0-0.86973 0.16881-1.2012 0.50586l-1.4688 1.4941h4.3418c0.082839-0.52789-0.072596-1.0872-0.47266-1.4941-0.33144-0.33705-0.76482-0.50586-1.1992-0.50586z" fill="#ff7070"/> +<path transform="translate(0 1036.4)" d="m10.633 3-1.9668 2h4.8008l1.0352-1.0527c0.2628-0.2673 0.41824-0.60049 0.47266-0.94727h-4.3418z" fill="#ffeb70"/> +<path transform="translate(0,1036.4)" d="m8.666 5-1.9648 2h2.8809c0.25686-0.33847 0.49465-0.66934 0.68555-1 0.33885-0.5859 0.95098-0.96109 1.627-0.99609 0.44399-0.023642 0.86385 0.115 1.2188 0.35547l0.35352-0.35938h-4.8008z" fill="#9dff70"/> +</g> +<path d="m1.2617 13c-0.08284 0.52789 0.072596 1.0872 0.47266 1.4941 0.33144 0.33705 0.76484 0.50586 1.1992 0.50586 0.4344 0 0.8697-0.16881 1.2012-0.50586l1.4688-1.4941h-4.3418zm7.9219 0c0.41312 1.1628 1.5119 2 2.8164 2s2.4033-0.83718 2.8164-2h-5.6328z" fill="#ff70ac"/> +<path d="m2.7695 11-1.0352 1.0527c-0.2628 0.2673-0.41824 0.60049-0.47266 0.94727h4.3418l1.4238-1.4473c0.020288-0.18998 0.04923-0.37542 0.089844-0.55273h-4.3477zm6.4609 0c-0.13656 0.32585-0.23047 0.65576-0.23047 1 0 0.35235 0.072014 0.68593 0.18359 1h5.6328c0.11158-0.31407 0.18359-0.64765 0.18359-1 0-0.34424-0.093909-0.67415-0.23047-1h-5.5391z" fill="#9f70ff"/> +<path d="m4.7363 9-1.9668 2h4.3477c0.17955-0.78395 0.54577-1.4354 0.9375-2h-3.3184zm5.8281 0c-0.55248 0.69003-1.0583 1.3421-1.334 2h5.5391c-0.2757-0.65786-0.78149-1.31-1.334-2h-2.8711z" fill="#70deff"/> +<path d="m6.7012 7-1.9648 2h3.3184c0.14116-0.20345 0.28508-0.40233 0.42383-0.58398 0.38601-0.5053 0.7635-0.96796 1.1035-1.416h-2.8809zm5.2988 0c-0.43047 0.7456-0.94456 1.3867-1.4355 2h2.8711c-0.49104-0.6133-1.0051-1.2544-1.4355-2z" fill="#70ffb9"/> +</svg> diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 449124acec..adfdfee603 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -176,7 +176,6 @@ Error ColladaImport::_create_scene_skeletons(Collada::Node *p_node) { Skeleton *sk = memnew(Skeleton); int bone = 0; - sk->set_use_bones_in_world_transform(true); // This improves compatibility in Collada for (int i = 0; i < p_node->children.size(); i++) { _populate_skeleton(sk, p_node->children[i], bone, -1); diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp index 9ea7c86e0c..79658c5a4c 100644 --- a/editor/import/editor_scene_importer_gltf.cpp +++ b/editor/import/editor_scene_importer_gltf.cpp @@ -31,9 +31,12 @@ #include "editor_scene_importer_gltf.h" #include "core/crypto/crypto_core.h" #include "core/io/json.h" +#include "core/math/disjoint_set.h" #include "core/math/math_defs.h" #include "core/os/file_access.h" #include "core/os/os.h" +#include "modules/regex/regex.h" +#include "scene/3d/bone_attachment.h" #include "scene/3d/camera.h" #include "scene/3d/mesh_instance.h" #include "scene/animation/animation_player.h" @@ -152,14 +155,21 @@ static Transform _arr_to_xform(const Array &p_array) { return xform; } +String EditorSceneImporterGLTF::_sanitize_scene_name(const String &name) { + RegEx regex("([^a-zA-Z0-9_ ]+)"); + String p_name = regex.sub(name, "", true); + return p_name; +} + String EditorSceneImporterGLTF::_gen_unique_name(GLTFState &state, const String &p_name) { - int index = 1; + const String s_name = _sanitize_scene_name(p_name); String name; + int index = 1; while (true) { + name = s_name; - name = p_name; if (index > 1) { name += " " + itos(index); } @@ -174,20 +184,63 @@ String EditorSceneImporterGLTF::_gen_unique_name(GLTFState &state, const String return name; } +String EditorSceneImporterGLTF::_sanitize_bone_name(const String &name) { + String p_name = name.camelcase_to_underscore(true); + + RegEx pattern_del("([^a-zA-Z0-9_ ])+"); + p_name = pattern_del.sub(p_name, "", true); + + RegEx pattern_nospace(" +"); + p_name = pattern_nospace.sub(p_name, "_", true); + + RegEx pattern_multiple("_+"); + p_name = pattern_multiple.sub(p_name, "_", true); + + RegEx pattern_padded("0+(\\d+)"); + p_name = pattern_padded.sub(p_name, "$1", true); + + return p_name; +} + +String EditorSceneImporterGLTF::_gen_unique_bone_name(GLTFState &state, const GLTFSkeletonIndex skel_i, const String &p_name) { + + const String s_name = _sanitize_bone_name(p_name); + + String name; + int index = 1; + while (true) { + name = s_name; + + if (index > 1) { + name += "_" + itos(index); + } + if (!state.skeletons[skel_i].unique_names.has(name)) { + break; + } + index++; + } + + state.skeletons.write[skel_i].unique_names.insert(name); + + return name; +} + Error EditorSceneImporterGLTF::_parse_scenes(GLTFState &state) { ERR_FAIL_COND_V(!state.json.has("scenes"), ERR_FILE_CORRUPT); - Array scenes = state.json["scenes"]; + const Array &scenes = state.json["scenes"]; for (int i = 0; i < 1; i++) { //only first scene is imported - Dictionary s = scenes[i]; + const Dictionary &s = scenes[i]; ERR_FAIL_COND_V(!s.has("nodes"), ERR_UNAVAILABLE); - Array nodes = s["nodes"]; + const Array &nodes = s["nodes"]; for (int j = 0; j < nodes.size(); j++) { state.root_nodes.push_back(nodes[j]); } - if (s.has("name")) { - state.scene_name = s["name"]; + if (s.has("name") && s["name"] != "") { + state.scene_name = _gen_unique_name(state, s["name"]); + } else { + state.scene_name = _gen_unique_name(state, "Scene"); } } @@ -197,11 +250,11 @@ Error EditorSceneImporterGLTF::_parse_scenes(GLTFState &state) { Error EditorSceneImporterGLTF::_parse_nodes(GLTFState &state) { ERR_FAIL_COND_V(!state.json.has("nodes"), ERR_FILE_CORRUPT); - Array nodes = state.json["nodes"]; + const Array &nodes = state.json["nodes"]; for (int i = 0; i < nodes.size(); i++) { GLTFNode *node = memnew(GLTFNode); - Dictionary n = nodes[i]; + const Dictionary &n = nodes[i]; if (n.has("name")) { node->name = n["name"]; @@ -214,13 +267,6 @@ Error EditorSceneImporterGLTF::_parse_nodes(GLTFState &state) { } if (n.has("skin")) { node->skin = n["skin"]; - /* - if (!state.skin_users.has(node->skin)) { - state.skin_users[node->skin] = Vector<int>(); - } - - state.skin_users[node->skin].push_back(i); - */ } if (n.has("matrix")) { node->xform = _arr_to_xform(n["matrix"]); @@ -242,7 +288,7 @@ Error EditorSceneImporterGLTF::_parse_nodes(GLTFState &state) { } if (n.has("children")) { - Array children = n["children"]; + const Array &children = n["children"]; for (int j = 0; j < children.size(); j++) { node->children.push_back(children[j]); } @@ -251,22 +297,46 @@ Error EditorSceneImporterGLTF::_parse_nodes(GLTFState &state) { state.nodes.push_back(node); } - //build the hierarchy + // build the hierarchy + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); node_i++) { - for (int i = 0; i < state.nodes.size(); i++) { + for (int j = 0; j < state.nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = state.nodes[node_i]->children[j]; - for (int j = 0; j < state.nodes[i]->children.size(); j++) { - int child = state.nodes[i]->children[j]; - ERR_FAIL_INDEX_V(child, state.nodes.size(), ERR_FILE_CORRUPT); - ERR_CONTINUE(state.nodes[child]->parent != -1); //node already has a parent, wtf. + ERR_FAIL_INDEX_V(child_i, state.nodes.size(), ERR_FILE_CORRUPT); + ERR_CONTINUE(state.nodes[child_i]->parent != -1); //node already has a parent, wtf. - state.nodes[child]->parent = i; + state.nodes[child_i]->parent = node_i; } } + _compute_node_heights(state); + return OK; } +void EditorSceneImporterGLTF::_compute_node_heights(GLTFState &state) { + + state.root_nodes.clear(); + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { + GLTFNode *node = state.nodes[node_i]; + node->height = 0; + + GLTFNodeIndex current_i = node_i; + while (current_i >= 0) { + const GLTFNodeIndex parent_i = state.nodes[current_i]->parent; + if (parent_i >= 0) { + ++node->height; + } + current_i = parent_i; + } + + if (node->height == 0) { + state.root_nodes.push_back(node_i); + } + } +} + static Vector<uint8_t> _parse_base64_uri(const String &uri) { int start = uri.find(","); @@ -292,14 +362,14 @@ Error EditorSceneImporterGLTF::_parse_buffers(GLTFState &state, const String &p_ if (!state.json.has("buffers")) return OK; - Array buffers = state.json["buffers"]; - for (int i = 0; i < buffers.size(); i++) { + const Array &buffers = state.json["buffers"]; + for (GLTFBufferIndex i = 0; i < buffers.size(); i++) { if (i == 0 && state.glb_data.size()) { state.buffers.push_back(state.glb_data); } else { - Dictionary buffer = buffers[i]; + const Dictionary &buffer = buffers[i]; if (buffer.has("uri")) { Vector<uint8_t> buffer_data; @@ -331,10 +401,10 @@ Error EditorSceneImporterGLTF::_parse_buffers(GLTFState &state, const String &p_ Error EditorSceneImporterGLTF::_parse_buffer_views(GLTFState &state) { ERR_FAIL_COND_V(!state.json.has("bufferViews"), ERR_FILE_CORRUPT); - Array buffers = state.json["bufferViews"]; - for (int i = 0; i < buffers.size(); i++) { + const Array &buffers = state.json["bufferViews"]; + for (GLTFBufferViewIndex i = 0; i < buffers.size(); i++) { - Dictionary d = buffers[i]; + const Dictionary &d = buffers[i]; GLTFBufferView buffer_view; @@ -352,7 +422,7 @@ Error EditorSceneImporterGLTF::_parse_buffer_views(GLTFState &state) { } if (d.has("target")) { - int target = d["target"]; + const int target = d["target"]; buffer_view.indices = target == ELEMENT_ARRAY_BUFFER; } @@ -389,10 +459,10 @@ EditorSceneImporterGLTF::GLTFType EditorSceneImporterGLTF::_get_type_from_str(co Error EditorSceneImporterGLTF::_parse_accessors(GLTFState &state) { ERR_FAIL_COND_V(!state.json.has("accessors"), ERR_FILE_CORRUPT); - Array accessors = state.json["accessors"]; - for (int i = 0; i < accessors.size(); i++) { + const Array &accessors = state.json["accessors"]; + for (GLTFAccessorIndex i = 0; i < accessors.size(); i++) { - Dictionary d = accessors[i]; + const Dictionary &d = accessors[i]; GLTFAccessor accessor; @@ -422,12 +492,12 @@ Error EditorSceneImporterGLTF::_parse_accessors(GLTFState &state) { if (d.has("sparse")) { //eeh.. - Dictionary s = d["sparse"]; + const Dictionary &s = d["sparse"]; ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR); accessor.sparse_count = d["count"]; ERR_FAIL_COND_V(!d.has("indices"), ERR_PARSE_ERROR); - Dictionary si = d["indices"]; + const Dictionary &si = d["indices"]; ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR); accessor.sparse_indices_buffer_view = si["bufferView"]; @@ -439,7 +509,7 @@ Error EditorSceneImporterGLTF::_parse_accessors(GLTFState &state) { } ERR_FAIL_COND_V(!d.has("values"), ERR_PARSE_ERROR); - Dictionary sv = d["values"]; + const Dictionary &sv = d["values"]; ERR_FAIL_COND_V(!sv.has("bufferView"), ERR_PARSE_ERROR); accessor.sparse_values_buffer_view = sv["bufferView"]; @@ -456,7 +526,7 @@ Error EditorSceneImporterGLTF::_parse_accessors(GLTFState &state) { return OK; } -String EditorSceneImporterGLTF::_get_component_type_name(uint32_t p_component) { +String EditorSceneImporterGLTF::_get_component_type_name(const uint32_t p_component) { switch (p_component) { case COMPONENT_TYPE_BYTE: return "Byte"; @@ -470,7 +540,7 @@ String EditorSceneImporterGLTF::_get_component_type_name(uint32_t p_component) { return "<Error>"; } -String EditorSceneImporterGLTF::_get_type_name(GLTFType p_component) { +String EditorSceneImporterGLTF::_get_type_name(const GLTFType p_component) { static const char *names[] = { "float", @@ -485,7 +555,7 @@ String EditorSceneImporterGLTF::_get_type_name(GLTFType p_component) { return names[p_component]; } -Error EditorSceneImporterGLTF::_decode_buffer_view(GLTFState &state, int p_buffer_view, double *dst, int skip_every, int skip_bytes, int element_size, int count, GLTFType type, int component_count, int component_type, int component_size, bool normalized, int byte_offset, bool for_vertex) { +Error EditorSceneImporterGLTF::_decode_buffer_view(GLTFState &state, double *dst, const GLTFBufferViewIndex p_buffer_view, const int skip_every, const int skip_bytes, const int element_size, const int count, const GLTFType type, const int component_count, const int component_type, const int component_size, const bool normalized, const int byte_offset, const bool for_vertex) { const GLTFBufferView &bv = state.buffer_views[p_buffer_view]; @@ -496,7 +566,7 @@ Error EditorSceneImporterGLTF::_decode_buffer_view(GLTFState &state, int p_buffe ERR_FAIL_INDEX_V(bv.buffer, state.buffers.size(), ERR_PARSE_ERROR); - uint32_t offset = bv.byte_offset + byte_offset; + const uint32_t offset = bv.byte_offset + byte_offset; Vector<uint8_t> buffer = state.buffers[bv.buffer]; //copy on write, so no performance hit const uint8_t *bufptr = buffer.ptr(); @@ -504,7 +574,7 @@ Error EditorSceneImporterGLTF::_decode_buffer_view(GLTFState &state, int p_buffe print_verbose("glTF: type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); print_verbose("glTF: accessor offset" + itos(byte_offset) + " view offset: " + itos(bv.byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv.byte_length)); - int buffer_end = (stride * (count - 1)) + element_size; + const int buffer_end = (stride * (count - 1)) + element_size; ERR_FAIL_COND_V(buffer_end > bv.byte_length, ERR_PARSE_ERROR); ERR_FAIL_COND_V((int)(offset + buffer_end) > buffer.size(), ERR_PARSE_ERROR); @@ -573,7 +643,7 @@ Error EditorSceneImporterGLTF::_decode_buffer_view(GLTFState &state, int p_buffe return OK; } -int EditorSceneImporterGLTF::_get_component_type_size(int component_type) { +int EditorSceneImporterGLTF::_get_component_type_size(const int component_type) { switch (component_type) { case COMPONENT_TYPE_BYTE: return 1; break; @@ -589,7 +659,7 @@ int EditorSceneImporterGLTF::_get_component_type_size(int component_type) { return 0; } -Vector<double> EditorSceneImporterGLTF::_decode_accessor(GLTFState &state, int p_accessor, bool p_for_vertex) { +Vector<double> EditorSceneImporterGLTF::_decode_accessor(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { //spec, for reference: //https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment @@ -598,12 +668,12 @@ Vector<double> EditorSceneImporterGLTF::_decode_accessor(GLTFState &state, int p const GLTFAccessor &a = state.accessors[p_accessor]; - int component_count_for_type[7] = { + const int component_count_for_type[7] = { 1, 2, 3, 4, 4, 9, 16 }; - int component_count = component_count_for_type[a.type]; - int component_size = _get_component_type_size(a.component_type); + const int component_count = component_count_for_type[a.type]; + const int component_size = _get_component_type_size(a.component_type); ERR_FAIL_COND_V(component_size == 0, Vector<double>()); int element_size = component_count * component_size; @@ -646,7 +716,7 @@ Vector<double> EditorSceneImporterGLTF::_decode_accessor(GLTFState &state, int p ERR_FAIL_INDEX_V(a.buffer_view, state.buffer_views.size(), Vector<double>()); - Error err = _decode_buffer_view(state, a.buffer_view, dst, skip_every, skip_bytes, element_size, a.count, a.type, component_count, a.component_type, component_size, a.normalized, a.byte_offset, p_for_vertex); + const Error err = _decode_buffer_view(state, dst, a.buffer_view, skip_every, skip_bytes, element_size, a.count, a.type, component_count, a.component_type, component_size, a.normalized, a.byte_offset, p_for_vertex); if (err != OK) return Vector<double>(); @@ -661,20 +731,20 @@ Vector<double> EditorSceneImporterGLTF::_decode_accessor(GLTFState &state, int p // I could not find any file using this, so this code is so far untested Vector<double> indices; indices.resize(a.sparse_count); - int indices_component_size = _get_component_type_size(a.sparse_indices_component_type); + const int indices_component_size = _get_component_type_size(a.sparse_indices_component_type); - Error err = _decode_buffer_view(state, a.sparse_indices_buffer_view, indices.ptrw(), 0, 0, indices_component_size, a.sparse_count, TYPE_SCALAR, 1, a.sparse_indices_component_type, indices_component_size, false, a.sparse_indices_byte_offset, false); + Error err = _decode_buffer_view(state, indices.ptrw(), a.sparse_indices_buffer_view, 0, 0, indices_component_size, a.sparse_count, TYPE_SCALAR, 1, a.sparse_indices_component_type, indices_component_size, false, a.sparse_indices_byte_offset, false); if (err != OK) return Vector<double>(); Vector<double> data; data.resize(component_count * a.sparse_count); - err = _decode_buffer_view(state, a.sparse_values_buffer_view, data.ptrw(), skip_every, skip_bytes, element_size, a.sparse_count, a.type, component_count, a.component_type, component_size, a.normalized, a.sparse_values_byte_offset, p_for_vertex); + err = _decode_buffer_view(state, data.ptrw(), a.sparse_values_buffer_view, skip_every, skip_bytes, element_size, a.sparse_count, a.type, component_count, a.component_type, component_size, a.normalized, a.sparse_values_byte_offset, p_for_vertex); if (err != OK) return Vector<double>(); for (int i = 0; i < indices.size(); i++) { - int write_offset = int(indices[i]) * component_count; + const int write_offset = int(indices[i]) * component_count; for (int j = 0; j < component_count; j++) { dst[write_offset + j] = data[i * component_count + j]; @@ -685,14 +755,16 @@ Vector<double> EditorSceneImporterGLTF::_decode_accessor(GLTFState &state, int p return dst_buffer; } -PoolVector<int> EditorSceneImporterGLTF::_decode_accessor_as_ints(GLTFState &state, int p_accessor, bool p_for_vertex) { +PoolVector<int> EditorSceneImporterGLTF::_decode_accessor_as_ints(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); PoolVector<int> ret; + if (attribs.size() == 0) return ret; + const double *attribs_ptr = attribs.ptr(); - int ret_size = attribs.size(); + const int ret_size = attribs.size(); ret.resize(ret_size); { PoolVector<int>::Write w = ret.write(); @@ -703,14 +775,16 @@ PoolVector<int> EditorSceneImporterGLTF::_decode_accessor_as_ints(GLTFState &sta return ret; } -PoolVector<float> EditorSceneImporterGLTF::_decode_accessor_as_floats(GLTFState &state, int p_accessor, bool p_for_vertex) { +PoolVector<float> EditorSceneImporterGLTF::_decode_accessor_as_floats(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); PoolVector<float> ret; + if (attribs.size() == 0) return ret; + const double *attribs_ptr = attribs.ptr(); - int ret_size = attribs.size(); + const int ret_size = attribs.size(); ret.resize(ret_size); { PoolVector<float>::Write w = ret.write(); @@ -721,15 +795,17 @@ PoolVector<float> EditorSceneImporterGLTF::_decode_accessor_as_floats(GLTFState return ret; } -PoolVector<Vector2> EditorSceneImporterGLTF::_decode_accessor_as_vec2(GLTFState &state, int p_accessor, bool p_for_vertex) { +PoolVector<Vector2> EditorSceneImporterGLTF::_decode_accessor_as_vec2(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); PoolVector<Vector2> ret; + if (attribs.size() == 0) return ret; + ERR_FAIL_COND_V(attribs.size() % 2 != 0, ret); const double *attribs_ptr = attribs.ptr(); - int ret_size = attribs.size() / 2; + const int ret_size = attribs.size() / 2; ret.resize(ret_size); { PoolVector<Vector2>::Write w = ret.write(); @@ -740,15 +816,17 @@ PoolVector<Vector2> EditorSceneImporterGLTF::_decode_accessor_as_vec2(GLTFState return ret; } -PoolVector<Vector3> EditorSceneImporterGLTF::_decode_accessor_as_vec3(GLTFState &state, int p_accessor, bool p_for_vertex) { +PoolVector<Vector3> EditorSceneImporterGLTF::_decode_accessor_as_vec3(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); PoolVector<Vector3> ret; + if (attribs.size() == 0) return ret; + ERR_FAIL_COND_V(attribs.size() % 3 != 0, ret); const double *attribs_ptr = attribs.ptr(); - int ret_size = attribs.size() / 3; + const int ret_size = attribs.size() / 3; ret.resize(ret_size); { PoolVector<Vector3>::Write w = ret.write(); @@ -758,13 +836,16 @@ PoolVector<Vector3> EditorSceneImporterGLTF::_decode_accessor_as_vec3(GLTFState } return ret; } -PoolVector<Color> EditorSceneImporterGLTF::_decode_accessor_as_color(GLTFState &state, int p_accessor, bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +PoolVector<Color> EditorSceneImporterGLTF::_decode_accessor_as_color(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); PoolVector<Color> ret; + if (attribs.size() == 0) return ret; - int type = state.accessors[p_accessor].type; + + const int type = state.accessors[p_accessor].type; ERR_FAIL_COND_V(!(type == TYPE_VEC3 || type == TYPE_VEC4), ret); int components; if (type == TYPE_VEC3) { @@ -772,9 +853,10 @@ PoolVector<Color> EditorSceneImporterGLTF::_decode_accessor_as_color(GLTFState & } else { // TYPE_VEC4 components = 4; } + ERR_FAIL_COND_V(attribs.size() % components != 0, ret); const double *attribs_ptr = attribs.ptr(); - int ret_size = attribs.size() / components; + const int ret_size = attribs.size() / components; ret.resize(ret_size); { PoolVector<Color>::Write w = ret.write(); @@ -784,15 +866,17 @@ PoolVector<Color> EditorSceneImporterGLTF::_decode_accessor_as_color(GLTFState & } return ret; } -Vector<Quat> EditorSceneImporterGLTF::_decode_accessor_as_quat(GLTFState &state, int p_accessor, bool p_for_vertex) { +Vector<Quat> EditorSceneImporterGLTF::_decode_accessor_as_quat(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); Vector<Quat> ret; + if (attribs.size() == 0) return ret; + ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); const double *attribs_ptr = attribs.ptr(); - int ret_size = attribs.size() / 4; + const int ret_size = attribs.size() / 4; ret.resize(ret_size); { for (int i = 0; i < ret_size; i++) { @@ -801,12 +885,14 @@ Vector<Quat> EditorSceneImporterGLTF::_decode_accessor_as_quat(GLTFState &state, } return ret; } -Vector<Transform2D> EditorSceneImporterGLTF::_decode_accessor_as_xform2d(GLTFState &state, int p_accessor, bool p_for_vertex) { +Vector<Transform2D> EditorSceneImporterGLTF::_decode_accessor_as_xform2d(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); Vector<Transform2D> ret; + if (attribs.size() == 0) return ret; + ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); ret.resize(attribs.size() / 4); for (int i = 0; i < ret.size(); i++) { @@ -816,12 +902,14 @@ Vector<Transform2D> EditorSceneImporterGLTF::_decode_accessor_as_xform2d(GLTFSta return ret; } -Vector<Basis> EditorSceneImporterGLTF::_decode_accessor_as_basis(GLTFState &state, int p_accessor, bool p_for_vertex) { +Vector<Basis> EditorSceneImporterGLTF::_decode_accessor_as_basis(GLTFState &state, const GLTFAccessorIndex p_accessor, bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); Vector<Basis> ret; + if (attribs.size() == 0) return ret; + ERR_FAIL_COND_V(attribs.size() % 9 != 0, ret); ret.resize(attribs.size() / 9); for (int i = 0; i < ret.size(); i++) { @@ -831,12 +919,15 @@ Vector<Basis> EditorSceneImporterGLTF::_decode_accessor_as_basis(GLTFState &stat } return ret; } -Vector<Transform> EditorSceneImporterGLTF::_decode_accessor_as_xform(GLTFState &state, int p_accessor, bool p_for_vertex) { - Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<Transform> EditorSceneImporterGLTF::_decode_accessor_as_xform(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); Vector<Transform> ret; + if (attribs.size() == 0) return ret; + ERR_FAIL_COND_V(attribs.size() % 16 != 0, ret); ret.resize(attribs.size() / 16); for (int i = 0; i < ret.size(); i++) { @@ -854,7 +945,7 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { return OK; Array meshes = state.json["meshes"]; - for (int i = 0; i < meshes.size(); i++) { + for (GLTFMeshIndex i = 0; i < meshes.size(); i++) { print_verbose("glTF: Parsing mesh: " + itos(i)); Dictionary d = meshes[i]; @@ -865,7 +956,7 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); Array primitives = d["primitives"]; - Dictionary extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); for (int j = 0; j < primitives.size(); j++) { @@ -880,7 +971,7 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; if (p.has("mode")) { - int mode = p["mode"]; + const int mode = p["mode"]; ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT); static const Mesh::PrimitiveType primitives2[7] = { Mesh::PRIMITIVE_POINTS, @@ -899,7 +990,6 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { if (a.has("POSITION")) { array[Mesh::ARRAY_VERTEX] = _decode_accessor_as_vec3(state, a["POSITION"], true); } - if (a.has("NORMAL")) { array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); } @@ -924,9 +1014,6 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { int wc = weights.size(); PoolVector<float>::Write w = weights.write(); - //PoolVector<int> v = array[Mesh::ARRAY_BONES]; - //PoolVector<int>::Read r = v.read(); - for (int k = 0; k < wc; k += 4) { float total = 0.0; total += w[k + 0]; @@ -939,36 +1026,34 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { w[k + 2] /= total; w[k + 3] /= total; } - - //print_verbose(itos(j / 4) + ": " + itos(r[j + 0]) + ":" + rtos(w[j + 0]) + ", " + itos(r[j + 1]) + ":" + rtos(w[j + 1]) + ", " + itos(r[j + 2]) + ":" + rtos(w[j + 2]) + ", " + itos(r[j + 3]) + ":" + rtos(w[j + 3])); } } array[Mesh::ARRAY_WEIGHTS] = weights; } if (p.has("indices")) { - PoolVector<int> indices = _decode_accessor_as_ints(state, p["indices"], false); if (primitive == Mesh::PRIMITIVE_TRIANGLES) { //swap around indices, convert ccw to cw for front face - int is = indices.size(); - PoolVector<int>::Write w = indices.write(); + const int is = indices.size(); + const PoolVector<int>::Write w = indices.write(); for (int k = 0; k < is; k += 3) { SWAP(w[k + 1], w[k + 2]); } } array[Mesh::ARRAY_INDEX] = indices; + } else if (primitive == Mesh::PRIMITIVE_TRIANGLES) { //generate indices because they need to be swapped for CW/CCW - PoolVector<Vector3> vertices = array[Mesh::ARRAY_VERTEX]; + const PoolVector<Vector3> &vertices = array[Mesh::ARRAY_VERTEX]; ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR); PoolVector<int> indices; - int vs = vertices.size(); + const int vs = vertices.size(); indices.resize(vs); { - PoolVector<int>::Write w = indices.write(); + const PoolVector<int>::Write w = indices.write(); for (int k = 0; k < vs; k += 3) { w[k] = k; w[k + 1] = k + 2; @@ -1002,23 +1087,23 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { //blend shapes if (p.has("targets")) { print_verbose("glTF: Mesh has targets"); - Array targets = p["targets"]; + const Array &targets = p["targets"]; //ideally BLEND_SHAPE_MODE_RELATIVE since gltf2 stores in displacement //but it could require a larger refactor? mesh.mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); if (j == 0) { - Array target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array(); + const Array &target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array(); for (int k = 0; k < targets.size(); k++) { - String name = k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k); + const String name = k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k); mesh.mesh->add_blend_shape(name); } } for (int k = 0; k < targets.size(); k++) { - Dictionary t = targets[k]; + const Dictionary &t = targets[k]; Array array_copy; array_copy.resize(Mesh::ARRAY_MAX); @@ -1031,17 +1116,17 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { if (t.has("POSITION")) { PoolVector<Vector3> varr = _decode_accessor_as_vec3(state, t["POSITION"], true); - PoolVector<Vector3> src_varr = array[Mesh::ARRAY_VERTEX]; - int size = src_varr.size(); + const PoolVector<Vector3> src_varr = array[Mesh::ARRAY_VERTEX]; + const int size = src_varr.size(); ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); { - int max_idx = varr.size(); + const int max_idx = varr.size(); varr.resize(size); - PoolVector<Vector3>::Write w_varr = varr.write(); - PoolVector<Vector3>::Read r_varr = varr.read(); - PoolVector<Vector3>::Read r_src_varr = src_varr.read(); + const PoolVector<Vector3>::Write w_varr = varr.write(); + const PoolVector<Vector3>::Read r_varr = varr.read(); + const PoolVector<Vector3>::Read r_src_varr = src_varr.read(); for (int l = 0; l < size; l++) { if (l < max_idx) { w_varr[l] = r_varr[l] + r_src_varr[l]; @@ -1054,16 +1139,16 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { } if (t.has("NORMAL")) { PoolVector<Vector3> narr = _decode_accessor_as_vec3(state, t["NORMAL"], true); - PoolVector<Vector3> src_narr = array[Mesh::ARRAY_NORMAL]; + const PoolVector<Vector3> src_narr = array[Mesh::ARRAY_NORMAL]; int size = src_narr.size(); ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); { int max_idx = narr.size(); narr.resize(size); - PoolVector<Vector3>::Write w_narr = narr.write(); - PoolVector<Vector3>::Read r_narr = narr.read(); - PoolVector<Vector3>::Read r_src_narr = src_narr.read(); + const PoolVector<Vector3>::Write w_narr = narr.write(); + const PoolVector<Vector3>::Read r_narr = narr.read(); + const PoolVector<Vector3>::Read r_src_narr = src_narr.read(); for (int l = 0; l < size; l++) { if (l < max_idx) { w_narr[l] = r_narr[l] + r_src_narr[l]; @@ -1075,21 +1160,22 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { array_copy[Mesh::ARRAY_NORMAL] = narr; } if (t.has("TANGENT")) { - PoolVector<Vector3> tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true); - PoolVector<float> tangents_v4; - PoolVector<float> src_tangents = array[Mesh::ARRAY_TANGENT]; + const PoolVector<Vector3> tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true); + const PoolVector<float> src_tangents = array[Mesh::ARRAY_TANGENT]; ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR); + PoolVector<float> tangents_v4; + { int max_idx = tangents_v3.size(); int size4 = src_tangents.size(); tangents_v4.resize(size4); - PoolVector<float>::Write w4 = tangents_v4.write(); + const PoolVector<float>::Write w4 = tangents_v4.write(); - PoolVector<Vector3>::Read r3 = tangents_v3.read(); - PoolVector<float>::Read r4 = src_tangents.read(); + const PoolVector<Vector3>::Read r3 = tangents_v3.read(); + const PoolVector<float>::Read r4 = src_tangents.read(); for (int l = 0; l < size4 / 4; l++) { @@ -1127,16 +1213,16 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { mesh.mesh->add_surface_from_arrays(primitive, array, morphs); if (p.has("material")) { - int material = p["material"]; + const int material = p["material"]; ERR_FAIL_INDEX_V(material, state.materials.size(), ERR_FILE_CORRUPT); - Ref<Material> mat = state.materials[material]; + const Ref<Material> &mat = state.materials[material]; mesh.mesh->surface_set_material(mesh.mesh->get_surface_count() - 1, mat); } } if (d.has("weights")) { - Array weights = d["weights"]; + const Array &weights = d["weights"]; ERR_FAIL_COND_V(mesh.mesh->get_blend_shape_count() != weights.size(), ERR_PARSE_ERROR); mesh.blend_weights.resize(weights.size()); for (int j = 0; j < weights.size(); j++) { @@ -1157,10 +1243,10 @@ Error EditorSceneImporterGLTF::_parse_images(GLTFState &state, const String &p_b if (!state.json.has("images")) return OK; - Array images = state.json["images"]; + const Array &images = state.json["images"]; for (int i = 0; i < images.size(); i++) { - Dictionary d = images[i]; + const Dictionary &d = images[i]; String mimetype; if (d.has("mimeType")) { @@ -1190,13 +1276,13 @@ Error EditorSceneImporterGLTF::_parse_images(GLTFState &state, const String &p_b } if (d.has("bufferView")) { - int bvi = d["bufferView"]; + const GLTFBufferViewIndex bvi = d["bufferView"]; ERR_FAIL_INDEX_V(bvi, state.buffer_views.size(), ERR_PARAMETER_RANGE_ERROR); const GLTFBufferView &bv = state.buffer_views[bvi]; - int bi = bv.buffer; + const GLTFBufferIndex bi = bv.buffer; ERR_FAIL_INDEX_V(bi, state.buffers.size(), ERR_PARAMETER_RANGE_ERROR); ERR_FAIL_COND_V(bv.byte_offset + bv.byte_length > state.buffers[bi].size(), ERR_FILE_CORRUPT); @@ -1209,7 +1295,7 @@ Error EditorSceneImporterGLTF::_parse_images(GLTFState &state, const String &p_b if (mimetype.findn("png") != -1) { //is a png - Ref<Image> img = Image::_png_mem_loader_func(data_ptr, data_size); + const Ref<Image> img = Image::_png_mem_loader_func(data_ptr, data_size); ERR_FAIL_COND_V(img.is_null(), ERR_FILE_CORRUPT); @@ -1223,7 +1309,7 @@ Error EditorSceneImporterGLTF::_parse_images(GLTFState &state, const String &p_b if (mimetype.findn("jpeg") != -1) { //is a jpg - Ref<Image> img = Image::_jpg_mem_loader_func(data_ptr, data_size); + const Ref<Image> img = Image::_jpg_mem_loader_func(data_ptr, data_size); ERR_FAIL_COND_V(img.is_null(), ERR_FILE_CORRUPT); @@ -1249,10 +1335,10 @@ Error EditorSceneImporterGLTF::_parse_textures(GLTFState &state) { if (!state.json.has("textures")) return OK; - Array textures = state.json["textures"]; - for (int i = 0; i < textures.size(); i++) { + const Array &textures = state.json["textures"]; + for (GLTFTextureIndex i = 0; i < textures.size(); i++) { - Dictionary d = textures[i]; + const Dictionary &d = textures[i]; ERR_FAIL_COND_V(!d.has("source"), ERR_PARSE_ERROR); @@ -1264,9 +1350,9 @@ Error EditorSceneImporterGLTF::_parse_textures(GLTFState &state) { return OK; } -Ref<Texture> EditorSceneImporterGLTF::_get_texture(GLTFState &state, int p_texture) { +Ref<Texture> EditorSceneImporterGLTF::_get_texture(GLTFState &state, const GLTFTextureIndex p_texture) { ERR_FAIL_INDEX_V(p_texture, state.textures.size(), Ref<Texture>()); - int image = state.textures[p_texture].src_image; + const GLTFImageIndex image = state.textures[p_texture].src_image; ERR_FAIL_INDEX_V(image, state.images.size(), Ref<Texture>()); @@ -1278,10 +1364,10 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { if (!state.json.has("materials")) return OK; - Array materials = state.json["materials"]; - for (int i = 0; i < materials.size(); i++) { + const Array &materials = state.json["materials"]; + for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { - Dictionary d = materials[i]; + const Dictionary &d = materials[i]; Ref<SpatialMaterial> material; material.instance(); @@ -1291,17 +1377,17 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { if (d.has("pbrMetallicRoughness")) { - Dictionary mr = d["pbrMetallicRoughness"]; + const Dictionary &mr = d["pbrMetallicRoughness"]; if (mr.has("baseColorFactor")) { - Array arr = mr["baseColorFactor"]; + const Array &arr = mr["baseColorFactor"]; ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); - Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); + const Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); material->set_albedo(c); } if (mr.has("baseColorTexture")) { - Dictionary bct = mr["baseColorTexture"]; + const Dictionary &bct = mr["baseColorTexture"]; if (bct.has("index")) { material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, _get_texture(state, bct["index"])); } @@ -1323,9 +1409,9 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { } if (mr.has("metallicRoughnessTexture")) { - Dictionary bct = mr["metallicRoughnessTexture"]; + const Dictionary &bct = mr["metallicRoughnessTexture"]; if (bct.has("index")) { - Ref<Texture> t = _get_texture(state, bct["index"]); + const Ref<Texture> t = _get_texture(state, bct["index"]); material->set_texture(SpatialMaterial::TEXTURE_METALLIC, t); material->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); material->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, t); @@ -1341,7 +1427,7 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { } if (d.has("normalTexture")) { - Dictionary bct = d["normalTexture"]; + const Dictionary &bct = d["normalTexture"]; if (bct.has("index")) { material->set_texture(SpatialMaterial::TEXTURE_NORMAL, _get_texture(state, bct["index"])); material->set_feature(SpatialMaterial::FEATURE_NORMAL_MAPPING, true); @@ -1351,7 +1437,7 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { } } if (d.has("occlusionTexture")) { - Dictionary bct = d["occlusionTexture"]; + const Dictionary &bct = d["occlusionTexture"]; if (bct.has("index")) { material->set_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION, _get_texture(state, bct["index"])); material->set_ao_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_RED); @@ -1360,16 +1446,16 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { } if (d.has("emissiveFactor")) { - Array arr = d["emissiveFactor"]; + const Array &arr = d["emissiveFactor"]; ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); - Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); + const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); material->set_emission(c); } if (d.has("emissiveTexture")) { - Dictionary bct = d["emissiveTexture"]; + const Dictionary &bct = d["emissiveTexture"]; if (bct.has("index")) { material->set_texture(SpatialMaterial::TEXTURE_EMISSION, _get_texture(state, bct["index"])); material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); @@ -1378,16 +1464,17 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { } if (d.has("doubleSided")) { - bool ds = d["doubleSided"]; + const bool ds = d["doubleSided"]; if (ds) { material->set_cull_mode(SpatialMaterial::CULL_DISABLED); } } if (d.has("alphaMode")) { - String am = d["alphaMode"]; + const String &am = d["alphaMode"]; if (am != "OPAQUE") { material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + material->set_depth_draw_mode(SpatialMaterial::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); } } @@ -1399,131 +1486,764 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { return OK; } +EditorSceneImporterGLTF::GLTFNodeIndex EditorSceneImporterGLTF::_find_highest_node(GLTFState &state, const Vector<GLTFNodeIndex> &subset) { + int heighest = -1; + GLTFNodeIndex best_node = -1; + + for (int i = 0; i < subset.size(); ++i) { + const GLTFNodeIndex node_i = subset[i]; + const GLTFNode *node = state.nodes[node_i]; + + if (heighest == -1 || node->height < heighest) { + heighest = node->height; + best_node = node_i; + } + } + + return best_node; +} + +bool EditorSceneImporterGLTF::_capture_nodes_in_skin(GLTFState &state, GLTFSkin &skin, const GLTFNodeIndex node_index) { + + bool found_joint = false; + + for (int i = 0; i < state.nodes[node_index]->children.size(); ++i) { + found_joint |= _capture_nodes_in_skin(state, skin, state.nodes[node_index]->children[i]); + } + + if (found_joint) { + // Mark it if we happen to find another skins joint... + if (state.nodes[node_index]->joint && skin.joints.find(node_index) < 0) { + skin.joints.push_back(node_index); + } else if (skin.non_joints.find(node_index) < 0) { + skin.non_joints.push_back(node_index); + } + } + + if (skin.joints.find(node_index) > 0) { + return true; + } + + return false; +} + +void EditorSceneImporterGLTF::_capture_nodes_for_multirooted_skin(GLTFState &state, GLTFSkin &skin) { + + DisjointSet<GLTFNodeIndex> disjoint_set; + + for (int i = 0; i < skin.joints.size(); ++i) { + const GLTFNodeIndex node_index = skin.joints[i]; + const GLTFNodeIndex parent = state.nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (skin.joints.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector<GLTFNodeIndex> roots; + disjoint_set.get_representatives(roots); + + if (roots.size() <= 1) { + return; + } + + int maxHeight = -1; + + // Determine the max height rooted tree + for (int i = 0; i < roots.size(); ++i) { + const GLTFNodeIndex root = roots[i]; + + if (maxHeight == -1 || state.nodes[root]->height < maxHeight) { + maxHeight = state.nodes[root]->height; + } + } + + // Go up the tree till all of the multiple roots of the skin are at the same hierarchy level. + // This sucks, but 99% of all game engines (not just Godot) would have this same issue. + for (int i = 0; i < roots.size(); ++i) { + + GLTFNodeIndex current_node = roots[i]; + while (state.nodes[current_node]->height > maxHeight) { + GLTFNodeIndex parent = state.nodes[current_node]->parent; + + if (state.nodes[parent]->joint && skin.joints.find(parent) < 0) { + skin.joints.push_back(parent); + } else if (skin.non_joints.find(parent) < 0) { + skin.non_joints.push_back(parent); + } + + current_node = parent; + } + + // replace the roots + roots.write[i] = current_node; + } + + // Climb up the tree until they all have the same parent + bool all_same; + + do { + all_same = true; + const GLTFNodeIndex first_parent = state.nodes[roots[0]]->parent; + + for (int i = 1; i < roots.size(); ++i) { + all_same &= (first_parent == state.nodes[roots[i]]->parent); + } + + if (!all_same) { + for (int i = 0; i < roots.size(); ++i) { + const GLTFNodeIndex current_node = roots[i]; + const GLTFNodeIndex parent = state.nodes[current_node]->parent; + + if (state.nodes[parent]->joint && skin.joints.find(parent) < 0) { + skin.joints.push_back(parent); + } else if (skin.non_joints.find(parent) < 0) { + skin.non_joints.push_back(parent); + } + + roots.write[i] = parent; + } + } + + } while (!all_same); +} + +Error EditorSceneImporterGLTF::_expand_skin(GLTFState &state, GLTFSkin &skin) { + + _capture_nodes_for_multirooted_skin(state, skin); + + // Grab all nodes that lay in between skin joints/nodes + DisjointSet<GLTFNodeIndex> disjoint_set; + + Vector<GLTFNodeIndex> all_skin_nodes; + all_skin_nodes.append_array(skin.joints); + all_skin_nodes.append_array(skin.non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state.nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector<GLTFNodeIndex> out_owners; + disjoint_set.get_representatives(out_owners); + + Vector<GLTFNodeIndex> out_roots; + + for (int i = 0; i < out_owners.size(); ++i) { + Vector<GLTFNodeIndex> set; + disjoint_set.get_members(set, out_owners[i]); + + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + out_roots.push_back(root); + } + + out_roots.sort(); + + for (int i = 0; i < out_roots.size(); ++i) { + _capture_nodes_in_skin(state, skin, out_roots[i]); + } + + skin.roots = out_roots; + + return OK; +} + +Error EditorSceneImporterGLTF::_verify_skin(GLTFState &state, GLTFSkin &skin) { + // Grab all nodes that lay in between skin joints/nodes + DisjointSet<GLTFNodeIndex> disjoint_set; + + Vector<GLTFNodeIndex> all_skin_nodes; + all_skin_nodes.append_array(skin.joints); + all_skin_nodes.append_array(skin.non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state.nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector<GLTFNodeIndex> out_roots; + disjoint_set.get_representatives(out_roots); + out_roots.sort(); + + ERR_FAIL_COND_V(out_roots.size() == 0, FAILED); + + ERR_FAIL_COND_V(out_roots.size() != skin.roots.size(), FAILED); + for (int i = 0; i < out_roots.size(); ++i) { + ERR_FAIL_COND_V(out_roots.size() != skin.roots.size(), FAILED); + } + + // Single rooted skin? Perfectly ok! + if (out_roots.size() == 1) { + return OK; + } + + // Make sure all parents of a multi-rooted skin are the SAME + const GLTFNodeIndex parent = state.nodes[out_roots[0]]->parent; + for (int i = 1; i < out_roots.size(); ++i) { + if (state.nodes[out_roots[i]]->parent != parent) { + return FAILED; + } + } + + return OK; +} + Error EditorSceneImporterGLTF::_parse_skins(GLTFState &state) { if (!state.json.has("skins")) return OK; - Array skins = state.json["skins"]; + const Array &skins = state.json["skins"]; + + // Create the base skins, and mark nodes that are joints for (int i = 0; i < skins.size(); i++) { - Dictionary d = skins[i]; + const Dictionary &d = skins[i]; GLTFSkin skin; ERR_FAIL_COND_V(!d.has("joints"), ERR_PARSE_ERROR); - Array joints = d["joints"]; - Vector<Transform> bind_matrices; + const Array &joints = d["joints"]; if (d.has("inverseBindMatrices")) { - bind_matrices = _decode_accessor_as_xform(state, d["inverseBindMatrices"], false); - ERR_FAIL_COND_V(bind_matrices.size() != joints.size(), ERR_PARSE_ERROR); + skin.inverse_binds = _decode_accessor_as_xform(state, d["inverseBindMatrices"], false); + ERR_FAIL_COND_V(skin.inverse_binds.size() != joints.size(), ERR_PARSE_ERROR); } for (int j = 0; j < joints.size(); j++) { - int index = joints[j]; - ERR_FAIL_INDEX_V(index, state.nodes.size(), ERR_PARSE_ERROR); - GLTFNode::Joint joint; - joint.skin = state.skins.size(); - joint.bone = j; - state.nodes[index]->joints.push_back(joint); - GLTFSkin::Bone bone; - bone.node = index; - if (bind_matrices.size()) { - bone.inverse_bind = bind_matrices[j]; - } + const GLTFNodeIndex node = joints[j]; + ERR_FAIL_INDEX_V(node, state.nodes.size(), ERR_PARSE_ERROR); - skin.bones.push_back(bone); + skin.joints.push_back(node); + skin.joints_original.push_back(node); + + state.nodes[node]->joint = true; + } + + if (d.has("name")) { + skin.name = d["name"]; } - print_verbose("glTF: Skin has skeleton? " + itos(d.has("skeleton"))); if (d.has("skeleton")) { - int skeleton = d["skeleton"]; - ERR_FAIL_INDEX_V(skeleton, state.nodes.size(), ERR_PARSE_ERROR); - print_verbose("glTF: Setting skeleton skin to" + itos(skeleton)); - skin.skeleton = skeleton; - if (!state.skeleton_nodes.has(skeleton)) { - state.skeleton_nodes[skeleton] = Vector<int>(); + skin.skin_root = d["skeleton"]; + } + + state.skins.push_back(skin); + } + + for (GLTFSkinIndex i = 0; i < state.skins.size(); ++i) { + GLTFSkin &skin = state.skins.write[i]; + + // Expand the skin to capture all the extra non-joints that lie in between the actual joints, + // and expand the hierarchy to ensure multi-rooted trees lie on the same height level + ERR_FAIL_COND_V(_expand_skin(state, skin), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(_verify_skin(state, skin), ERR_PARSE_ERROR); + } + + print_verbose("glTF: Total skins: " + itos(state.skins.size())); + + return OK; +} + +Error EditorSceneImporterGLTF::_determine_skeletons(GLTFState &state) { + + // Using a disjoint set, we are going to potentially combine all skins that are actually branches + // of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton. + // This is another unclear issue caused by the current glTF specification. + + DisjointSet<GLTFNodeIndex> skeleton_sets; + + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + const GLTFSkin &skin = state.skins[skin_i]; + + Vector<GLTFNodeIndex> all_skin_nodes; + all_skin_nodes.append_array(skin.joints); + all_skin_nodes.append_array(skin.non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state.nodes[node_index]->parent; + skeleton_sets.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + skeleton_sets.create_union(parent, node_index); } - state.skeleton_nodes[skeleton].push_back(i); } - if (d.has("name")) { - skin.name = d["name"]; + // We are going to connect the separate skin subtrees in each skin together + // so that the final roots are entire sets of valid skin trees + for (int i = 1; i < skin.roots.size(); ++i) { + skeleton_sets.create_union(skin.roots[0], skin.roots[i]); } + } - //locate the right place to put a Skeleton node - /* - if (state.skin_users.has(i)) { - Vector<int> users = state.skin_users[i]; - int skin_node = -1; - for (int j = 0; j < users.size(); j++) { - int user = state.nodes[users[j]]->parent; //always go from parent - if (j == 0) { - skin_node = user; - } else if (skin_node != -1) { - bool found = false; - while (skin_node >= 0) { - - int cuser = user; - while (cuser != -1) { - if (cuser == skin_node) { - found = true; - break; - } - cuser = state.nodes[skin_node]->parent; - } - if (found) - break; - skin_node = state.nodes[skin_node]->parent; - } + { // attempt to joint all touching subsets (siblings/parent are part of another skin) + Vector<GLTFNodeIndex> groups_representatives; + skeleton_sets.get_representatives(groups_representatives); + + Vector<GLTFNodeIndex> highest_group_members; + Vector<Vector<GLTFNodeIndex> > groups; + for (int i = 0; i < groups_representatives.size(); ++i) { + Vector<GLTFNodeIndex> group; + skeleton_sets.get_members(group, groups_representatives[i]); + highest_group_members.push_back(_find_highest_node(state, group)); + groups.push_back(group); + } + + for (int i = 0; i < highest_group_members.size(); ++i) { + const GLTFNodeIndex node_i = highest_group_members[i]; + + // Attach any siblings together (this needs to be done n^2/2 times) + for (int j = i + 1; j < highest_group_members.size(); ++j) { + const GLTFNodeIndex node_j = highest_group_members[j]; + + // Even if they are siblings under the root! :) + if (state.nodes[node_i]->parent == state.nodes[node_j]->parent) { + skeleton_sets.create_union(node_i, node_j); + } + } - if (!found) { - skin_node = -1; //just leave where it is + // Attach any parenting going on together (we need to do this n^2 times) + const GLTFNodeIndex node_i_parent = state.nodes[node_i]->parent; + if (node_i_parent >= 0) { + for (int j = 0; j < groups.size() && i != j; ++j) { + const Vector<GLTFNodeIndex> &group = groups[j]; + + if (group.find(node_i_parent) >= 0) { + const GLTFNodeIndex node_j = highest_group_members[j]; + skeleton_sets.create_union(node_i, node_j); } + } + } + } + } + + // At this point, the skeleton groups should be finalized + Vector<GLTFNodeIndex> skeleton_owners; + skeleton_sets.get_representatives(skeleton_owners); + + // Mark all the skins actual skeletons, after we have merged them + for (GLTFSkeletonIndex skel_i = 0; skel_i < skeleton_owners.size(); ++skel_i) { - //find a common parent + const GLTFNodeIndex skeleton_owner = skeleton_owners[skel_i]; + GLTFSkeleton skeleton; + + Vector<GLTFNodeIndex> skeleton_nodes; + skeleton_sets.get_members(skeleton_nodes, skeleton_owner); + + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + GLTFSkin &skin = state.skins.write[skin_i]; + + // If any of the the skeletons nodes exist in a skin, that skin now maps to the skeleton + for (int i = 0; i < skeleton_nodes.size(); ++i) { + GLTFNodeIndex skel_node_i = skeleton_nodes[i]; + if (skin.joints.find(skel_node_i) >= 0 || skin.non_joints.find(skel_node_i) >= 0) { + skin.skeleton = skel_i; + continue; } } + } + + Vector<GLTFNodeIndex> non_joints; + for (int i = 0; i < skeleton_nodes.size(); ++i) { + const GLTFNodeIndex node_i = skeleton_nodes[i]; + + if (state.nodes[node_i]->joint) { + skeleton.joints.push_back(node_i); + } else { + non_joints.push_back(node_i); + } + } + + state.skeletons.push_back(skeleton); + + _reparent_non_joint_skeleton_subtrees(state, state.skeletons.write[skel_i], non_joints); + } + + for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); ++skel_i) { + GLTFSkeleton &skeleton = state.skeletons.write[skel_i]; + + for (int i = 0; i < skeleton.joints.size(); ++i) { + const GLTFNodeIndex node_i = skeleton.joints[i]; + GLTFNode *node = state.nodes[node_i]; + + ERR_FAIL_COND_V(!node->joint, ERR_PARSE_ERROR); + ERR_FAIL_COND_V(node->skeleton >= 0, ERR_PARSE_ERROR); + node->skeleton = skel_i; + } + + ERR_FAIL_COND_V(_determine_skeleton_roots(state, skel_i), ERR_PARSE_ERROR); + } + + return OK; +} + +Error EditorSceneImporterGLTF::_reparent_non_joint_skeleton_subtrees(GLTFState &state, GLTFSkeleton &skeleton, const Vector<GLTFNodeIndex> &non_joints) { + + DisjointSet<GLTFNodeIndex> subtree_set; + + // Populate the disjoint set with ONLY non joints that are in the skeleton hierarchy (non_joints vector) + // This way we can find any joints that lie in between joints, as the current glTF specification + // mentions nothing about non-joints being in between joints of the same skin. Hopefully one day we + // can remove this code. + + // skinD depicted here explains this issue: + // https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Animation_Skin + + for (int i = 0; i < non_joints.size(); ++i) { + const GLTFNodeIndex node_i = non_joints[i]; + + subtree_set.insert(node_i); + + const GLTFNodeIndex parent_i = state.nodes[node_i]->parent; + if (parent_i >= 0 && non_joints.find(parent_i) >= 0 && !state.nodes[parent_i]->joint) { + subtree_set.create_union(parent_i, node_i); + } + } + + // Find all the non joint subtrees and re-parent them to a new "fake" joint + + Vector<GLTFNodeIndex> non_joint_subtree_roots; + subtree_set.get_representatives(non_joint_subtree_roots); + + for (int root_i = 0; root_i < non_joint_subtree_roots.size(); ++root_i) { + const GLTFNodeIndex subtree_root = non_joint_subtree_roots[root_i]; + + Vector<GLTFNodeIndex> subtree_nodes; + subtree_set.get_members(subtree_nodes, subtree_root); + + for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) { + ERR_FAIL_COND_V(_reparent_to_fake_joint(state, skeleton, subtree_nodes[subtree_i]), FAILED); + + // We modified the tree, recompute all the heights + _compute_node_heights(state); + } + } + + return OK; +} + +Error EditorSceneImporterGLTF::_reparent_to_fake_joint(GLTFState &state, GLTFSkeleton &skeleton, const GLTFNodeIndex node_index) { + GLTFNode *node = state.nodes[node_index]; + + // Can we just "steal" this joint if it is just a spatial node? + if (node->skin < 0 && node->mesh < 0 && node->camera < 0) { + node->joint = true; + // Add the joint to the skeletons joints + skeleton.joints.push_back(node_index); + return OK; + } + + GLTFNode *fake_joint = memnew(GLTFNode); + const GLTFNodeIndex fake_joint_index = state.nodes.size(); + state.nodes.push_back(fake_joint); + + // We better not be a joint, or we messed up in our logic + if (node->joint == true) + return FAILED; + + fake_joint->translation = node->translation; + fake_joint->rotation = node->rotation; + fake_joint->scale = node->scale; + fake_joint->xform = node->xform; + fake_joint->joint = true; + + // We can use the exact same name here, because the joint will be inside a skeleton and not the scene + fake_joint->name = node->name; + + // Clear the nodes transforms, since it will be parented to the fake joint + node->translation = Vector3(0, 0, 0); + node->rotation = Quat(); + node->scale = Vector3(1, 1, 1); + node->xform = Transform(); + + // Transfer the node children to the fake joint + for (int child_i = 0; child_i < node->children.size(); ++child_i) { + GLTFNode *child = state.nodes[node->children[child_i]]; + child->parent = fake_joint_index; + } + + fake_joint->children = node->children; + node->children.clear(); + + // add the fake joint to the parent and remove the original joint + if (node->parent >= 0) { + GLTFNode *parent = state.nodes[node->parent]; + parent->children.erase(node_index); + parent->children.push_back(fake_joint_index); + fake_joint->parent = node->parent; + } + + // Add the node to the fake joint + fake_joint->children.push_back(node_index); + node->parent = fake_joint_index; + node->fake_joint_parent = fake_joint_index; + + // Add the fake joint to the skeletons joints + skeleton.joints.push_back(fake_joint_index); + + // Replace skin_skeletons with fake joints if we must. + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + GLTFSkin &skin = state.skins.write[skin_i]; + if (skin.skin_root == node_index) { + skin.skin_root = fake_joint_index; + } + } + + return OK; +} + +Error EditorSceneImporterGLTF::_determine_skeleton_roots(GLTFState &state, const GLTFSkeletonIndex skel_i) { + + DisjointSet<GLTFNodeIndex> disjoint_set; + + for (GLTFNodeIndex i = 0; i < state.nodes.size(); ++i) { + const GLTFNode *node = state.nodes[i]; + + if (node->skeleton != skel_i) { + continue; + } + + disjoint_set.insert(i); + + if (node->parent >= 0 && state.nodes[node->parent]->skeleton == skel_i) { + disjoint_set.create_union(node->parent, i); + } + } + + GLTFSkeleton &skeleton = state.skeletons.write[skel_i]; + + Vector<GLTFNodeIndex> owners; + disjoint_set.get_representatives(owners); + + Vector<GLTFNodeIndex> roots; + + for (int i = 0; i < owners.size(); ++i) { + Vector<GLTFNodeIndex> set; + disjoint_set.get_members(set, owners[i]); + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + roots.push_back(root); + } + + roots.sort(); + + skeleton.roots = roots; + + if (roots.size() == 0) { + return FAILED; + } else if (roots.size() == 1) { + return OK; + } + + // Check that the subtrees have the same parent root + const GLTFNodeIndex parent = state.nodes[roots[0]]->parent; + for (int i = 1; i < roots.size(); ++i) { + if (state.nodes[roots[i]]->parent != parent) { + return FAILED; + } + } + + return OK; +} + +Error EditorSceneImporterGLTF::_create_skeletons(GLTFState &state) { + for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); ++skel_i) { + + GLTFSkeleton &gltf_skeleton = state.skeletons.write[skel_i]; + + Skeleton *skeleton = memnew(Skeleton); + gltf_skeleton.godot_skeleton = skeleton; + + // Make a unique name, no gltf node represents this skeleton + skeleton->set_name(_gen_unique_name(state, "Skeleton")); + + List<GLTFNodeIndex> bones; - if (skin_node != -1) { - for (int j = 0; j < users.size(); j++) { - state.nodes[users[j]]->child_of_skeleton = i; + for (int i = 0; i < gltf_skeleton.roots.size(); ++i) { + bones.push_back(gltf_skeleton.roots[i]); + } + + // Make the skeleton creation deterministic by going through the roots in + // a sorted order, and DEPTH FIRST + bones.sort(); + + while (!bones.empty()) { + const GLTFNodeIndex node_i = bones.front()->get(); + bones.pop_front(); + + GLTFNode *node = state.nodes[node_i]; + ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED); + + { // Add all child nodes to the stack (deterministically) + Vector<GLTFNodeIndex> child_nodes; + for (int i = 0; i < node->children.size(); ++i) { + const GLTFNodeIndex child_i = node->children[i]; + if (state.nodes[child_i]->skeleton == skel_i) { + child_nodes.push_back(child_i); + } } - state.nodes[skin_node]->skeleton_children.push_back(i); + // Depth first insertion + child_nodes.sort(); + for (int i = child_nodes.size() - 1; i >= 0; --i) { + bones.push_front(child_nodes[i]); + } } + + const int bone_index = skeleton->get_bone_count(); + + if (node->name.empty()) { + node->name = "bone"; + } + + node->name = _gen_unique_bone_name(state, skel_i, node->name); + + skeleton->add_bone(node->name); + skeleton->set_bone_rest(bone_index, node->xform); + skeleton->set_bone_pose(bone_index, node->xform); + + if (node->parent >= 0 && state.nodes[node->parent]->skeleton == skel_i) { + const int bone_parent = skeleton->find_bone(state.nodes[node->parent]->name); + ERR_FAIL_COND_V(bone_parent < 0, FAILED); + skeleton->set_bone_parent(bone_index, skeleton->find_bone(state.nodes[node->parent]->name)); + } + + state.scene_nodes.insert(node_i, skeleton); } - */ - state.skins.push_back(skin); } - print_verbose("glTF: Total skins: " + itos(state.skins.size())); - //now + ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(state), ERR_PARSE_ERROR); + + return OK; +} + +Error EditorSceneImporterGLTF::_map_skin_joints_indices_to_skeleton_bone_indices(GLTFState &state) { + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + GLTFSkin &skin = state.skins.write[skin_i]; + + const GLTFSkeleton &skeleton = state.skeletons[skin.skeleton]; + + for (int joint_index = 0; joint_index < skin.joints_original.size(); ++joint_index) { + const GLTFNodeIndex node_i = skin.joints_original[joint_index]; + const GLTFNode *node = state.nodes[node_i]; + + const int bone_index = skeleton.godot_skeleton->find_bone(node->name); + ERR_FAIL_COND_V(bone_index < 0, FAILED); + + skin.joint_i_to_bone_i.insert(joint_index, bone_index); + } + } + + return OK; +} + +Error EditorSceneImporterGLTF::_create_skins(GLTFState &state) { + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + GLTFSkin &gltf_skin = state.skins.write[skin_i]; + + Ref<Skin> skin; + skin.instance(); + + // Some skins don't have IBM's! What absolute monsters! + const bool has_ibms = !gltf_skin.inverse_binds.empty(); + + for (int joint_i = 0; joint_i < gltf_skin.joints_original.size(); ++joint_i) { + int bone_i = gltf_skin.joint_i_to_bone_i[joint_i]; + + if (has_ibms) { + skin->add_bind(bone_i, gltf_skin.inverse_binds[joint_i]); + } else { + skin->add_bind(bone_i, Transform()); + } + } + + gltf_skin.godot_skin = skin; + } + + // Purge the duplicates! + _remove_duplicate_skins(state); + + // Create unique names now, after removing duplicates + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + Ref<Skin> skin = state.skins[skin_i].godot_skin; + if (skin->get_name().empty()) { + // Make a unique name, no gltf node represents this skin + skin->set_name(_gen_unique_name(state, "Skin")); + } + } return OK; } +bool EditorSceneImporterGLTF::_skins_are_same(const Ref<Skin> &skin_a, const Ref<Skin> &skin_b) { + if (skin_a->get_bind_count() != skin_b->get_bind_count()) { + return false; + } + + for (int i = 0; i < skin_a->get_bind_count(); ++i) { + + if (skin_a->get_bind_bone(i) != skin_b->get_bind_bone(i)) { + return false; + } + + Transform a_xform = skin_a->get_bind_pose(i); + Transform b_xform = skin_b->get_bind_pose(i); + + if (a_xform != b_xform) { + return false; + } + } + + return true; +} + +void EditorSceneImporterGLTF::_remove_duplicate_skins(GLTFState &state) { + for (int i = 0; i < state.skins.size(); ++i) { + for (int j = i + 1; j < state.skins.size(); ++j) { + const Ref<Skin> &skin_i = state.skins[i].godot_skin; + const Ref<Skin> &skin_j = state.skins[j].godot_skin; + + if (_skins_are_same(skin_i, skin_j)) { + // replace it and delete the old + state.skins.write[j].godot_skin = skin_i; + } + } + } +} + Error EditorSceneImporterGLTF::_parse_cameras(GLTFState &state) { if (!state.json.has("cameras")) return OK; - Array cameras = state.json["cameras"]; + const Array &cameras = state.json["cameras"]; - for (int i = 0; i < cameras.size(); i++) { + for (GLTFCameraIndex i = 0; i < cameras.size(); i++) { - Dictionary d = cameras[i]; + const Dictionary &d = cameras[i]; GLTFCamera camera; ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - String type = d["type"]; + const String &type = d["type"]; if (type == "orthographic") { camera.perspective = false; if (d.has("orthographic")) { - Dictionary og = d["orthographic"]; + const Dictionary &og = d["orthographic"]; camera.fov_size = og["ymag"]; camera.zfar = og["zfar"]; camera.znear = og["znear"]; @@ -1535,7 +2255,7 @@ Error EditorSceneImporterGLTF::_parse_cameras(GLTFState &state) { camera.perspective = true; if (d.has("perspective")) { - Dictionary ppt = d["perspective"]; + const Dictionary &ppt = d["perspective"]; // GLTF spec is in radians, Godot's camera is in degrees. camera.fov_size = (double)ppt["yfov"] * 180.0 / Math_PI; camera.zfar = ppt["zfar"]; @@ -1560,11 +2280,11 @@ Error EditorSceneImporterGLTF::_parse_animations(GLTFState &state) { if (!state.json.has("animations")) return OK; - Array animations = state.json["animations"]; + const Array &animations = state.json["animations"]; - for (int i = 0; i < animations.size(); i++) { + for (GLTFAnimationIndex i = 0; i < animations.size(); i++) { - Dictionary d = animations[i]; + const Dictionary &d = animations[i]; GLTFAnimation animation; @@ -1580,25 +2300,25 @@ Error EditorSceneImporterGLTF::_parse_animations(GLTFState &state) { for (int j = 0; j < channels.size(); j++) { - Dictionary c = channels[j]; + const Dictionary &c = channels[j]; if (!c.has("target")) continue; - Dictionary t = c["target"]; + const Dictionary &t = c["target"]; if (!t.has("node") || !t.has("path")) { continue; } ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR); - int sampler = c["sampler"]; + const int sampler = c["sampler"]; ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR); - int node = t["node"]; + GLTFNodeIndex node = t["node"]; String path = t["path"]; ERR_FAIL_INDEX_V(node, state.nodes.size(), ERR_PARSE_ERROR); - GLTFAnimation::Track *track = NULL; + GLTFAnimation::Track *track = nullptr; if (!animation.tracks.has(node)) { animation.tracks[node] = GLTFAnimation::Track(); @@ -1606,17 +2326,17 @@ Error EditorSceneImporterGLTF::_parse_animations(GLTFState &state) { track = &animation.tracks[node]; - Dictionary s = samplers[sampler]; + const Dictionary &s = samplers[sampler]; ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR); ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR); - int input = s["input"]; - int output = s["output"]; + const int input = s["input"]; + const int output = s["output"]; GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR; if (s.has("interpolation")) { - String in = s["interpolation"]; + const String &in = s["interpolation"]; if (in == "STEP") { interp = GLTFAnimation::INTERP_STEP; } else if (in == "LINEAR") { @@ -1628,33 +2348,33 @@ Error EditorSceneImporterGLTF::_parse_animations(GLTFState &state) { } } - PoolVector<float> times = _decode_accessor_as_floats(state, input, false); + const PoolVector<float> times = _decode_accessor_as_floats(state, input, false); if (path == "translation") { - PoolVector<Vector3> translations = _decode_accessor_as_vec3(state, output, false); + const PoolVector<Vector3> translations = _decode_accessor_as_vec3(state, output, false); track->translation_track.interpolation = interp; track->translation_track.times = Variant(times); //convert via variant track->translation_track.values = Variant(translations); //convert via variant } else if (path == "rotation") { - Vector<Quat> rotations = _decode_accessor_as_quat(state, output, false); + const Vector<Quat> rotations = _decode_accessor_as_quat(state, output, false); track->rotation_track.interpolation = interp; track->rotation_track.times = Variant(times); //convert via variant track->rotation_track.values = rotations; //convert via variant } else if (path == "scale") { - PoolVector<Vector3> scales = _decode_accessor_as_vec3(state, output, false); + const PoolVector<Vector3> scales = _decode_accessor_as_vec3(state, output, false); track->scale_track.interpolation = interp; track->scale_track.times = Variant(times); //convert via variant track->scale_track.values = Variant(scales); //convert via variant } else if (path == "weights") { - PoolVector<float> weights = _decode_accessor_as_floats(state, output, false); + const PoolVector<float> weights = _decode_accessor_as_floats(state, output, false); ERR_FAIL_INDEX_V(state.nodes[node]->mesh, state.meshes.size(), ERR_PARSE_ERROR); const GLTFMesh *mesh = &state.meshes[state.nodes[node]->mesh]; ERR_FAIL_COND_V(mesh->blend_weights.size() == 0, ERR_PARSE_ERROR); - int wc = mesh->blend_weights.size(); + const int wc = mesh->blend_weights.size(); track->weight_tracks.resize(wc); - int wlen = weights.size() / wc; + const int wlen = weights.size() / wc; PoolVector<float>::Read r = weights.read(); for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea GLTFAnimation::Channel<float> cf; @@ -1686,11 +2406,16 @@ void EditorSceneImporterGLTF::_assign_scene_names(GLTFState &state) { for (int i = 0; i < state.nodes.size(); i++) { GLTFNode *n = state.nodes[i]; - if (n->name == "") { + + // Any joints get unique names generated when the skeleton is made, unique to the skeleton + if (n->skeleton >= 0) + continue; + + if (n->name.empty()) { if (n->mesh >= 0) { n->name = "Mesh"; - } else if (n->joints.size()) { - n->name = "Bone"; + } else if (n->camera >= 0) { + n->name = "Camera"; } else { n->name = "Node"; } @@ -1700,127 +2425,131 @@ void EditorSceneImporterGLTF::_assign_scene_names(GLTFState &state) { } } -void EditorSceneImporterGLTF::_reparent_skeleton(GLTFState &state, int p_node, Vector<Skeleton *> &skeletons, Node *p_parent_node) { - //reparent skeletons to proper place - Vector<int> nodes = state.skeleton_nodes[p_node]; - for (int i = 0; i < nodes.size(); i++) { - Skeleton *skeleton = skeletons[nodes[i]]; - Node *owner = skeleton->get_owner(); - skeleton->get_parent()->remove_child(skeleton); - p_parent_node->add_child(skeleton); - skeleton->set_owner(owner); - //may have meshes as children, set owner in them too - for (int j = 0; j < skeleton->get_child_count(); j++) { - skeleton->get_child(j)->set_owner(owner); - } - } -} +BoneAttachment *EditorSceneImporterGLTF::_generate_bone_attachment(GLTFState &state, Skeleton *skeleton, const GLTFNodeIndex node_index) { -void EditorSceneImporterGLTF::_generate_node(GLTFState &state, int p_node, Node *p_parent, Node *p_owner, Vector<Skeleton *> &skeletons) { - ERR_FAIL_INDEX(p_node, state.nodes.size()); + const GLTFNode *gltf_node = state.nodes[node_index]; + const GLTFNode *bone_node = state.nodes[gltf_node->parent]; - GLTFNode *n = state.nodes[p_node]; - Spatial *node; + BoneAttachment *bone_attachment = memnew(BoneAttachment); + print_verbose("glTF: Creating bone attachment for: " + gltf_node->name); - if (n->mesh >= 0) { - ERR_FAIL_INDEX(n->mesh, state.meshes.size()); - MeshInstance *mi = memnew(MeshInstance); - print_verbose("glTF: Creating mesh for: " + n->name); - GLTFMesh &mesh = state.meshes.write[n->mesh]; - mi->set_mesh(mesh.mesh); - if (mesh.mesh->get_name() == "") { - mesh.mesh->set_name(n->name); - } - for (int i = 0; i < mesh.blend_weights.size(); i++) { - mi->set("blend_shapes/" + mesh.mesh->get_blend_shape_name(i), mesh.blend_weights[i]); - } + ERR_FAIL_COND_V(!bone_node->joint, nullptr); - node = mi; + bone_attachment->set_bone_name(bone_node->name); - } else if (n->camera >= 0) { - ERR_FAIL_INDEX(n->camera, state.cameras.size()); - Camera *camera = memnew(Camera); + return bone_attachment; +} - const GLTFCamera &c = state.cameras[n->camera]; - if (c.perspective) { - camera->set_perspective(c.fov_size, c.znear, c.znear); - } else { - camera->set_orthogonal(c.fov_size, c.znear, c.znear); - } +MeshInstance *EditorSceneImporterGLTF::_generate_mesh_instance(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { + const GLTFNode *gltf_node = state.nodes[node_index]; - node = camera; - } else { - node = memnew(Spatial); + ERR_FAIL_INDEX_V(gltf_node->mesh, state.meshes.size(), nullptr); + + MeshInstance *mi = memnew(MeshInstance); + print_verbose("glTF: Creating mesh for: " + gltf_node->name); + + GLTFMesh &mesh = state.meshes.write[gltf_node->mesh]; + mi->set_mesh(mesh.mesh); + + if (mesh.mesh->get_name() == "") { + mesh.mesh->set_name(gltf_node->name); + } + + for (int i = 0; i < mesh.blend_weights.size(); i++) { + mi->set("blend_shapes/" + mesh.mesh->get_blend_shape_name(i), mesh.blend_weights[i]); } - node->set_name(n->name); + return mi; +} - n->godot_nodes.push_back(node); +Camera *EditorSceneImporterGLTF::_generate_camera(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { + const GLTFNode *gltf_node = state.nodes[node_index]; - if (n->skin >= 0 && n->skin < skeletons.size() && Object::cast_to<MeshInstance>(node)) { - MeshInstance *mi = Object::cast_to<MeshInstance>(node); + ERR_FAIL_INDEX_V(gltf_node->camera, state.cameras.size(), nullptr); - Skeleton *s = skeletons[n->skin]; - s->add_child(node); //According to spec, mesh should actually act as a child of the skeleton, as it inherits its transform - mi->set_skeleton_path(String("..")); + Camera *camera = memnew(Camera); + print_verbose("glTF: Creating camera for: " + gltf_node->name); + const GLTFCamera &c = state.cameras[gltf_node->camera]; + if (c.perspective) { + camera->set_perspective(c.fov_size, c.znear, c.znear); } else { - p_parent->add_child(node); - node->set_transform(n->xform); + camera->set_orthogonal(c.fov_size, c.znear, c.znear); } - node->set_owner(p_owner); + return camera; +} -#if 0 - for (int i = 0; i < n->skeleton_children.size(); i++) { +Spatial *EditorSceneImporterGLTF::_generate_spatial(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { + const GLTFNode *gltf_node = state.nodes[node_index]; - Skeleton *s = skeletons[n->skeleton_children[i]]; - s->get_parent()->remove_child(s); - node->add_child(s); - s->set_owner(p_owner); - } -#endif - for (int i = 0; i < n->children.size(); i++) { - if (state.nodes[n->children[i]]->joints.size()) { - _generate_bone(state, n->children[i], skeletons, node); - } else { - _generate_node(state, n->children[i], node, p_owner, skeletons); - } - } + Spatial *spatial = memnew(Spatial); + print_verbose("glTF: Creating spatial for: " + gltf_node->name); - if (state.skeleton_nodes.has(p_node)) { - _reparent_skeleton(state, p_node, skeletons, node); - } + return spatial; } -void EditorSceneImporterGLTF::_generate_bone(GLTFState &state, int p_node, Vector<Skeleton *> &skeletons, Node *p_parent_node) { - ERR_FAIL_INDEX(p_node, state.nodes.size()); +void EditorSceneImporterGLTF::_generate_scene_node(GLTFState &state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) { + + const GLTFNode *gltf_node = state.nodes[node_index]; + + Spatial *current_node = nullptr; + + // Is our parent a skeleton + Skeleton *active_skeleton = Object::cast_to<Skeleton>(scene_parent); + + if (gltf_node->skeleton >= 0) { + Skeleton *skeleton = state.skeletons[gltf_node->skeleton].godot_skeleton; - if (state.skeleton_nodes.has(p_node)) { - _reparent_skeleton(state, p_node, skeletons, p_parent_node); + if (active_skeleton != skeleton) { + ERR_FAIL_COND_MSG(active_skeleton != nullptr, "glTF: Generating scene detected direct parented Skeletons"); + + // Add it to the scene if it has not already been added + if (skeleton->get_parent() == nullptr) { + scene_parent->add_child(skeleton); + skeleton->set_owner(scene_root); + } + } + + active_skeleton = skeleton; + current_node = skeleton; } - GLTFNode *n = state.nodes[p_node]; + // If we have an active skeleton, and the node is node skinned, we need to create a bone attachment + if (current_node == nullptr && active_skeleton != nullptr && gltf_node->skin < 0) { + BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index); - for (int i = 0; i < n->joints.size(); i++) { - const int skin = n->joints[i].skin; - ERR_FAIL_COND(skin < 0); + scene_parent->add_child(bone_attachment); + bone_attachment->set_owner(scene_root); - Skeleton *s = skeletons[skin]; - const GLTFNode *gltf_bone_node = state.nodes[state.skins[skin].bones[n->joints[i].bone].node]; - const String bone_name = gltf_bone_node->name; - const int parent = gltf_bone_node->parent; - const int parent_index = s->find_bone(state.nodes[parent]->name); + // There is no gltf_node that represent this, so just directly create a unique name + bone_attachment->set_name(_gen_unique_name(state, "BoneAttachment")); - const int bone_index = s->find_bone(bone_name); - s->set_bone_parent(bone_index, parent_index); + // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node + // and attach it to the bone_attachment + scene_parent = bone_attachment; + } + + // We still have not managed to make a node + if (current_node == nullptr) { + if (gltf_node->mesh >= 0) { + current_node = _generate_mesh_instance(state, scene_parent, node_index); + } else if (gltf_node->camera >= 0) { + current_node = _generate_camera(state, scene_parent, node_index); + } else { + current_node = _generate_spatial(state, scene_parent, node_index); + } - n->godot_nodes.push_back(s); - n->joints.write[i].godot_bone_index = bone_index; + scene_parent->add_child(current_node); + current_node->set_owner(scene_root); + current_node->set_transform(gltf_node->xform); + current_node->set_name(gltf_node->name); } - for (int i = 0; i < n->children.size(); i++) { - _generate_bone(state, n->children[i], skeletons, p_parent_node); + state.scene_nodes.insert(node_index, current_node); + + for (int i = 0; i < gltf_node->children.size(); ++i) { + _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]); } } @@ -1834,43 +2563,43 @@ struct EditorSceneImporterGLTFInterpolate { T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) { - float t2 = t * t; - float t3 = t2 * t; + const float t2 = t * t; + const float t3 = t2 * t; return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); } T bezier(T start, T control_1, T control_2, T end, float t) { /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; + const real_t omt = (1.0 - t); + const real_t omt2 = omt * omt; + const real_t omt3 = omt2 * omt; + const real_t t2 = t * t; + const real_t t3 = t2 * t; return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; } }; -//thank you for existing, partial specialization +// thank you for existing, partial specialization template <> struct EditorSceneImporterGLTFInterpolate<Quat> { - Quat lerp(const Quat &a, const Quat &b, float c) const { + Quat lerp(const Quat &a, const Quat &b, const float c) const { ERR_FAIL_COND_V(!a.is_normalized(), Quat()); ERR_FAIL_COND_V(!b.is_normalized(), Quat()); return a.slerp(b, c).normalized(); } - Quat catmull_rom(const Quat &p0, const Quat &p1, const Quat &p2, const Quat &p3, float c) { + Quat catmull_rom(const Quat &p0, const Quat &p1, const Quat &p2, const Quat &p3, const float c) { ERR_FAIL_COND_V(!p1.is_normalized(), Quat()); ERR_FAIL_COND_V(!p2.is_normalized(), Quat()); return p1.slerp(p2, c).normalized(); } - Quat bezier(Quat start, Quat control_1, Quat control_2, Quat end, float t) { + Quat bezier(const Quat start, const Quat control_1, const Quat control_2, const Quat end, const float t) { ERR_FAIL_COND_V(!start.is_normalized(), Quat()); ERR_FAIL_COND_V(!end.is_normalized(), Quat()); @@ -1879,7 +2608,7 @@ struct EditorSceneImporterGLTFInterpolate<Quat> { }; template <class T> -T EditorSceneImporterGLTF::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, GLTFAnimation::Interpolation p_interp) { +T EditorSceneImporterGLTF::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { //could use binary search, worth it? int idx = -1; @@ -1900,7 +2629,7 @@ T EditorSceneImporterGLTF::_interpolate_track(const Vector<float> &p_times, cons return p_values[p_times.size() - 1]; } - float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); return interp.lerp(p_values[idx], p_values[idx + 1], c); @@ -1924,7 +2653,7 @@ T EditorSceneImporterGLTF::_interpolate_track(const Vector<float> &p_times, cons return p_values[1 + p_times.size() - 1]; } - float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); return interp.catmull_rom(p_values[idx - 1], p_values[idx], p_values[idx + 1], p_values[idx + 3], c); @@ -1937,12 +2666,12 @@ T EditorSceneImporterGLTF::_interpolate_track(const Vector<float> &p_times, cons return p_values[(p_times.size() - 1) * 3 + 1]; } - float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - T from = p_values[idx * 3 + 1]; - T c1 = from + p_values[idx * 3 + 2]; - T to = p_values[idx * 3 + 4]; - T c2 = to + p_values[idx * 3 + 3]; + const T from = p_values[idx * 3 + 1]; + const T c1 = from + p_values[idx * 3 + 2]; + const T to = p_values[idx * 3 + 4]; + const T c2 = to + p_values[idx * 3 + 3]; return interp.bezier(from, c1, c2, to, c); @@ -1952,12 +2681,13 @@ T EditorSceneImporterGLTF::_interpolate_track(const Vector<float> &p_times, cons ERR_FAIL_V(p_values[0]); } -void EditorSceneImporterGLTF::_import_animation(GLTFState &state, AnimationPlayer *ap, int index, int bake_fps, Vector<Skeleton *> skeletons) { +void EditorSceneImporterGLTF::_import_animation(GLTFState &state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) { const GLTFAnimation &anim = state.animations[index]; String name = anim.name; - if (name == "") { + if (name.empty()) { + // No node represent these, and they are not in the hierarchy, so just make a unique name name = _gen_unique_name(state, "Animation"); } @@ -1973,102 +2703,143 @@ void EditorSceneImporterGLTF::_import_animation(GLTFState &state, AnimationPlaye //need to find the path NodePath node_path; - GLTFNode *node = state.nodes[E->key()]; - for (int n = 0; n < node->godot_nodes.size(); n++) { + GLTFNodeIndex node_index = E->key(); + if (state.nodes[node_index]->fake_joint_parent >= 0) { + // Should be same as parent + node_index = state.nodes[node_index]->fake_joint_parent; + } - if (node->joints.size()) { - Skeleton *sk = (Skeleton *)node->godot_nodes[n]; - String path = ap->get_parent()->get_path_to(sk); - String bone = sk->get_bone_name(node->joints[n].godot_bone_index); - node_path = path + ":" + bone; - } else { - node_path = ap->get_parent()->get_path_to(node->godot_nodes[n]); - } + const GLTFNode *node = state.nodes[E->key()]; - for (int i = 0; i < track.rotation_track.times.size(); i++) { - length = MAX(length, track.rotation_track.times[i]); - } - for (int i = 0; i < track.translation_track.times.size(); i++) { - length = MAX(length, track.translation_track.times[i]); + if (node->skeleton >= 0) { + const Skeleton *sk = Object::cast_to<Skeleton>(state.scene_nodes.find(node_index)->get()); + ERR_FAIL_COND(sk == nullptr); + + const String path = ap->get_parent()->get_path_to(sk); + const String bone = node->name; + node_path = path + ":" + bone; + } else { + node_path = ap->get_parent()->get_path_to(state.scene_nodes.find(node_index)->get()); + } + + for (int i = 0; i < track.rotation_track.times.size(); i++) { + length = MAX(length, track.rotation_track.times[i]); + } + for (int i = 0; i < track.translation_track.times.size(); i++) { + length = MAX(length, track.translation_track.times[i]); + } + for (int i = 0; i < track.scale_track.times.size(); i++) { + length = MAX(length, track.scale_track.times[i]); + } + + for (int i = 0; i < track.weight_tracks.size(); i++) { + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + length = MAX(length, track.weight_tracks[i].times[j]); } - for (int i = 0; i < track.scale_track.times.size(); i++) { - length = MAX(length, track.scale_track.times[i]); + } + + if (track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) { + //make transform track + int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_TRANSFORM); + animation->track_set_path(track_idx, node_path); + //first determine animation length + + const float increment = 1.0 / float(bake_fps); + float time = 0.0; + + Vector3 base_pos; + Quat base_rot; + Vector3 base_scale = Vector3(1, 1, 1); + + if (!track.rotation_track.values.size()) { + base_rot = state.nodes[E->key()]->rotation.normalized(); } - for (int i = 0; i < track.weight_tracks.size(); i++) { - for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { - length = MAX(length, track.weight_tracks[i].times[j]); - } + if (!track.translation_track.values.size()) { + base_pos = state.nodes[E->key()]->translation; } - if (track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) { - //make transform track - int track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_TRANSFORM); - animation->track_set_path(track_idx, node_path); - //first determine animation length + if (!track.scale_track.values.size()) { + base_scale = state.nodes[E->key()]->scale; + } - float increment = 1.0 / float(bake_fps); - float time = 0.0; + bool last = false; + while (true) { - Vector3 base_pos; - Quat base_rot; - Vector3 base_scale = Vector3(1, 1, 1); + Vector3 pos = base_pos; + Quat rot = base_rot; + Vector3 scale = base_scale; - if (!track.rotation_track.values.size()) { - base_rot = state.nodes[E->key()]->rotation.normalized(); + if (track.translation_track.times.size()) { + pos = _interpolate_track<Vector3>(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation); } - if (!track.translation_track.values.size()) { - base_pos = state.nodes[E->key()]->translation; + if (track.rotation_track.times.size()) { + rot = _interpolate_track<Quat>(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); } - if (!track.scale_track.values.size()) { - base_scale = state.nodes[E->key()]->scale; + if (track.scale_track.times.size()) { + scale = _interpolate_track<Vector3>(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); } - bool last = false; - while (true) { - - Vector3 pos = base_pos; - Quat rot = base_rot; - Vector3 scale = base_scale; - - if (track.translation_track.times.size()) { + if (node->skeleton >= 0) { - pos = _interpolate_track<Vector3>(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation); - } - - if (track.rotation_track.times.size()) { - - rot = _interpolate_track<Quat>(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); - } + Transform xform; + xform.basis.set_quat_scale(rot, scale); + xform.origin = pos; - if (track.scale_track.times.size()) { + const Skeleton *skeleton = state.skeletons[node->skeleton].godot_skeleton; + const int bone_idx = skeleton->find_bone(node->name); + xform = skeleton->get_bone_rest(bone_idx).affine_inverse() * xform; - scale = _interpolate_track<Vector3>(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); - } + rot = xform.basis.get_rotation_quat(); + rot.normalize(); + scale = xform.basis.get_scale(); + pos = xform.origin; + } - if (node->joints.size()) { + animation->transform_track_insert_key(track_idx, time, pos, rot, scale); - Transform xform; - //xform.basis = Basis(rot); - //xform.basis.scale(scale); - xform.basis.set_quat_scale(rot, scale); - xform.origin = pos; + if (last) { + break; + } + time += increment; + if (time >= length) { + last = true; + time = length; + } + } + } - Skeleton *skeleton = skeletons[node->joints[n].skin]; - int bone = node->joints[n].godot_bone_index; - xform = skeleton->get_bone_rest(bone).affine_inverse() * xform; + for (int i = 0; i < track.weight_tracks.size(); i++) { + ERR_CONTINUE(node->mesh < 0 || node->mesh >= state.meshes.size()); + const GLTFMesh &mesh = state.meshes[node->mesh]; + const String prop = "blend_shapes/" + mesh.mesh->get_blend_shape_name(i); - rot = xform.basis.get_rotation_quat(); - rot.normalize(); - scale = xform.basis.get_scale(); - pos = xform.origin; - } + const String blend_path = String(node_path) + ":" + prop; - animation->transform_track_insert_key(track_idx, time, pos, rot, scale); + const int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_VALUE); + animation->track_set_path(track_idx, blend_path); + // Only LINEAR and STEP (NEAREST) can be supported out of the box by Godot's Animation, + // the other modes have to be baked. + GLTFAnimation::Interpolation gltf_interp = track.weight_tracks[i].interpolation; + if (gltf_interp == GLTFAnimation::INTERP_LINEAR || gltf_interp == GLTFAnimation::INTERP_STEP) { + animation->track_set_interpolation_type(track_idx, gltf_interp == GLTFAnimation::INTERP_STEP ? Animation::INTERPOLATION_NEAREST : Animation::INTERPOLATION_LINEAR); + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + const float t = track.weight_tracks[i].times[j]; + const float w = track.weight_tracks[i].values[j]; + animation->track_insert_key(track_idx, t, w); + } + } else { + // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. + const float increment = 1.0 / float(bake_fps); + float time = 0.0; + bool last = false; + while (true) { + _interpolate_track<float>(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); if (last) { break; } @@ -2079,86 +2850,54 @@ void EditorSceneImporterGLTF::_import_animation(GLTFState &state, AnimationPlaye } } } - - for (int i = 0; i < track.weight_tracks.size(); i++) { - ERR_CONTINUE(node->mesh < 0 || node->mesh >= state.meshes.size()); - const GLTFMesh &mesh = state.meshes[node->mesh]; - String prop = "blend_shapes/" + mesh.mesh->get_blend_shape_name(i); - node_path = String(node_path) + ":" + prop; - - int track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_VALUE); - animation->track_set_path(track_idx, node_path); - - // Only LINEAR and STEP (NEAREST) can be supported out of the box by Godot's Animation, - // the other modes have to be baked. - GLTFAnimation::Interpolation gltf_interp = track.weight_tracks[i].interpolation; - if (gltf_interp == GLTFAnimation::INTERP_LINEAR || gltf_interp == GLTFAnimation::INTERP_STEP) { - animation->track_set_interpolation_type(track_idx, gltf_interp == GLTFAnimation::INTERP_STEP ? Animation::INTERPOLATION_NEAREST : Animation::INTERPOLATION_LINEAR); - for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { - float t = track.weight_tracks[i].times[j]; - float w = track.weight_tracks[i].values[j]; - animation->track_insert_key(track_idx, t, w); - } - } else { - // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - float increment = 1.0 / float(bake_fps); - float time = 0.0; - bool last = false; - while (true) { - _interpolate_track<float>(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); - if (last) { - break; - } - time += increment; - if (time >= length) { - last = true; - time = length; - } - } - } - } } } + animation->set_length(length); ap->add_animation(name, animation); } -Spatial *EditorSceneImporterGLTF::_generate_scene(GLTFState &state, int p_bake_fps) { +void EditorSceneImporterGLTF::_process_mesh_instances(GLTFState &state, Spatial *scene_root) { + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { + const GLTFNode *node = state.nodes[node_i]; - Spatial *root = memnew(Spatial); - root->set_name(state.scene_name); - //generate skeletons - Vector<Skeleton *> skeletons; - for (int i = 0; i < state.skins.size(); i++) { - Skeleton *s = memnew(Skeleton); - s->set_use_bones_in_world_transform(false); //GLTF does not need this since meshes are always local - String name = state.skins[i].name; - if (name == "") { - name = _gen_unique_name(state, "Skeleton"); - } - for (int j = 0; j < state.skins[i].bones.size(); j++) { - s->add_bone(state.nodes[state.skins[i].bones[j].node]->name); - s->set_bone_rest(j, state.skins[i].bones[j].inverse_bind.affine_inverse()); - } - s->set_name(name); - root->add_child(s); - s->set_owner(root); - skeletons.push_back(s); - } - for (int i = 0; i < state.root_nodes.size(); i++) { - if (state.nodes[state.root_nodes[i]]->joints.size()) { - _generate_bone(state, state.root_nodes[i], skeletons, root); - } else { - _generate_node(state, state.root_nodes[i], root, root, skeletons); + if (node->skin >= 0 && node->mesh >= 0) { + const GLTFSkinIndex skin_i = node->skin; + + Map<GLTFNodeIndex, Node *>::Element *mi_element = state.scene_nodes.find(node_i); + MeshInstance *mi = Object::cast_to<MeshInstance>(mi_element->get()); + ERR_FAIL_COND(mi == nullptr); + + const GLTFSkeletonIndex skel_i = state.skins[node->skin].skeleton; + const GLTFSkeleton &gltf_skeleton = state.skeletons[skel_i]; + Skeleton *skeleton = gltf_skeleton.godot_skeleton; + ERR_FAIL_COND(skeleton == nullptr); + + mi->get_parent()->remove_child(mi); + skeleton->add_child(mi); + mi->set_owner(scene_root); + + mi->set_skin(state.skins[skin_i].godot_skin); + mi->set_skeleton_path(mi->get_path_to(skeleton)); + mi->set_transform(Transform()); } } +} - for (int i = 0; i < skeletons.size(); i++) { - skeletons[i]->localize_rests(); +Spatial *EditorSceneImporterGLTF::_generate_scene(GLTFState &state, const int p_bake_fps) { + + Spatial *root = memnew(Spatial); + + // scene_name is already unique + root->set_name(state.scene_name); + + for (int i = 0; i < state.root_nodes.size(); ++i) { + _generate_scene_node(state, root, root, state.root_nodes[i]); } + _process_mesh_instances(state, root); + if (state.animations.size()) { AnimationPlayer *ap = memnew(AnimationPlayer); ap->set_name("AnimationPlayer"); @@ -2166,7 +2905,7 @@ Spatial *EditorSceneImporterGLTF::_generate_scene(GLTFState &state, int p_bake_f ap->set_owner(root); for (int i = 0; i < state.animations.size(); i++) { - _import_animation(state, ap, i, p_bake_fps, skeletons); + _import_animation(state, ap, i, p_bake_fps); } } @@ -2241,30 +2980,45 @@ Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_fla if (err != OK) return NULL; - /* STEP 8 PARSE MESHES (we have enough info now) */ - err = _parse_meshes(state); + /* STEP 9 PARSE SKINS */ + err = _parse_skins(state); if (err != OK) return NULL; - /* STEP 9 PARSE SKINS */ - err = _parse_skins(state); + /* STEP 10 DETERMINE SKELETONS */ + err = _determine_skeletons(state); + if (err != OK) + return NULL; + + /* STEP 11 CREATE SKELETONS */ + err = _create_skeletons(state); + if (err != OK) + return NULL; + + /* STEP 12 CREATE SKINS */ + err = _create_skins(state); + if (err != OK) + return NULL; + + /* STEP 13 PARSE MESHES (we have enough info now) */ + err = _parse_meshes(state); if (err != OK) return NULL; - /* STEP 10 PARSE CAMERAS */ + /* STEP 14 PARSE CAMERAS */ err = _parse_cameras(state); if (err != OK) return NULL; - /* STEP 11 PARSE ANIMATIONS */ + /* STEP 15 PARSE ANIMATIONS */ err = _parse_animations(state); if (err != OK) return NULL; - /* STEP 12 ASSIGN SCENE NAMES */ + /* STEP 16 ASSIGN SCENE NAMES */ _assign_scene_names(state); - /* STEP 13 MAKE SCENE! */ + /* STEP 17 MAKE SCENE! */ Spatial *scene = _generate_scene(state, p_bake_fps); return scene; diff --git a/editor/import/editor_scene_importer_gltf.h b/editor/import/editor_scene_importer_gltf.h index ebf20e122a..6021bf10c8 100644 --- a/editor/import/editor_scene_importer_gltf.h +++ b/editor/import/editor_scene_importer_gltf.h @@ -36,11 +36,26 @@ #include "scene/3d/spatial.h" class AnimationPlayer; +class BoneAttachment; +class MeshInstance; class EditorSceneImporterGLTF : public EditorSceneImporter { GDCLASS(EditorSceneImporterGLTF, EditorSceneImporter); + typedef int GLTFAccessorIndex; + typedef int GLTFAnimationIndex; + typedef int GLTFBufferIndex; + typedef int GLTFBufferViewIndex; + typedef int GLTFCameraIndex; + typedef int GLTFImageIndex; + typedef int GLTFMaterialIndex; + typedef int GLTFMeshIndex; + typedef int GLTFNodeIndex; + typedef int GLTFSkeletonIndex; + typedef int GLTFSkinIndex; + typedef int GLTFTextureIndex; + enum { ARRAY_BUFFER = 34962, ELEMENT_ARRAY_BUFFER = 34963, @@ -61,8 +76,8 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { }; - String _get_component_type_name(uint32_t p_component); - int _get_component_type_size(int component_type); + String _get_component_type_name(const uint32_t p_component); + int _get_component_type_size(const int component_type); enum GLTFType { TYPE_SCALAR, @@ -74,60 +89,48 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { TYPE_MAT4, }; - String _get_type_name(GLTFType p_component); + String _get_type_name(const GLTFType p_component); struct GLTFNode { + //matrices need to be transformed to this - int parent; + GLTFNodeIndex parent; + int height; Transform xform; String name; - //Node *godot_node; - //int godot_bone_index; - - int mesh; - int camera; - int skin; - //int skeleton_skin; - //int child_of_skeleton; // put as children of skeleton - //Vector<int> skeleton_children; //skeleton put as children of this - - struct Joint { - int skin; - int bone; - int godot_bone_index; - - Joint() { - skin = -1; - bone = -1; - godot_bone_index = -1; - } - }; - Vector<Joint> joints; + GLTFMeshIndex mesh; + GLTFCameraIndex camera; + GLTFSkinIndex skin; + + GLTFSkeletonIndex skeleton; + bool joint; - //keep them for animation Vector3 translation; Quat rotation; Vector3 scale; Vector<int> children; - Vector<Node *> godot_nodes; + + GLTFNodeIndex fake_joint_parent; GLTFNode() : parent(-1), + height(-1), mesh(-1), camera(-1), skin(-1), - //skeleton_skin(-1), - //child_of_skeleton(-1), - scale(Vector3(1, 1, 1)) { - } + skeleton(-1), + joint(false), + translation(0, 0, 0), + scale(Vector3(1, 1, 1)), + fake_joint_parent(-1) {} }; struct GLTFBufferView { - int buffer; + GLTFBufferIndex buffer; int byte_offset; int byte_length; int byte_stride; @@ -135,7 +138,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { //matrices need to be transformed to this GLTFBufferView() : - buffer(0), + buffer(-1), byte_offset(0), byte_length(0), byte_stride(0), @@ -145,7 +148,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { struct GLTFAccessor { - int buffer_view; + GLTFBufferViewIndex buffer_view; int byte_offset; int component_type; bool normalized; @@ -160,8 +163,6 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { int sparse_values_buffer_view; int sparse_values_byte_offset; - //matrices need to be transformed to this - GLTFAccessor() { buffer_view = 0; byte_offset = 0; @@ -176,27 +177,67 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { } }; struct GLTFTexture { - int src_image; + GLTFImageIndex src_image; }; - struct GLTFSkin { + struct GLTFSkeleton { + // The *synthesized* skeletons joints + Vector<GLTFNodeIndex> joints; - String name; - struct Bone { - Transform inverse_bind; - int node; - }; + // The roots of the skeleton. If there are multiple, each root must have the same parent + // (ie roots are siblings) + Vector<GLTFNodeIndex> roots; - int skeleton; - Vector<Bone> bones; + // The created Skeleton for the scene + Skeleton *godot_skeleton; - //matrices need to be transformed to this + // Set of unique bone names for the skeleton + Set<String> unique_names; - GLTFSkin() { - skeleton = -1; + GLTFSkeleton() : + godot_skeleton(nullptr) { } }; + struct GLTFSkin { + String name; + + // The "skeleton" property defined in the gltf spec. -1 = Scene Root + GLTFNodeIndex skin_root; + + Vector<GLTFNodeIndex> joints_original; + Vector<Transform> inverse_binds; + + // Note: joints + non_joints should form a complete subtree, or subtrees with a common parent + + // All nodes that are skins that are caught in-between the original joints + // (inclusive of joints_original) + Vector<GLTFNodeIndex> joints; + + // All Nodes that are caught in-between skin joint nodes, and are not defined + // as joints by any skin + Vector<GLTFNodeIndex> non_joints; + + // The roots of the skin. In the case of multiple roots, their parent *must* + // be the same (the roots must be siblings) + Vector<GLTFNodeIndex> roots; + + // The GLTF Skeleton this Skin points to (after we determine skeletons) + GLTFSkeletonIndex skeleton; + + // A mapping from the joint indices (in the order of joints_original) to the + // Godot Skeleton's bone_indices + Map<int, int> joint_i_to_bone_i; + + // The Actual Skin that will be created as a mapping between the IBM's of this skin + // to the generated skeleton for the mesh instances. + Ref<Skin> godot_skin; + + GLTFSkin() : + skin_root(-1), + skeleton(-1) {} + }; + struct GLTFMesh { Ref<ArrayMesh> mesh; Vector<float> blend_weights; @@ -272,11 +313,10 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { Set<String> unique_names; + Vector<GLTFSkeleton> skeletons; Vector<GLTFAnimation> animations; - Map<int, Vector<int> > skeleton_nodes; - - //Map<int, Vector<int> > skin_users; //cache skin users + Map<GLTFNodeIndex, Node *> scene_nodes; ~GLTFState() { for (int i = 0; i < nodes.size(); i++) { @@ -285,37 +325,38 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { } }; + String _sanitize_scene_name(const String &name); String _gen_unique_name(GLTFState &state, const String &p_name); - Ref<Texture> _get_texture(GLTFState &state, int p_texture); + String _sanitize_bone_name(const String &name); + String _gen_unique_bone_name(GLTFState &state, const GLTFSkeletonIndex skel_i, const String &p_name); + + Ref<Texture> _get_texture(GLTFState &state, const GLTFTextureIndex p_texture); Error _parse_json(const String &p_path, GLTFState &state); Error _parse_glb(const String &p_path, GLTFState &state); Error _parse_scenes(GLTFState &state); Error _parse_nodes(GLTFState &state); + + void _compute_node_heights(GLTFState &state); + Error _parse_buffers(GLTFState &state, const String &p_base_path); Error _parse_buffer_views(GLTFState &state); GLTFType _get_type_from_str(const String &p_string); Error _parse_accessors(GLTFState &state); - Error _decode_buffer_view(GLTFState &state, int p_buffer_view, double *dst, int skip_every, int skip_bytes, int element_size, int count, GLTFType type, int component_count, int component_type, int component_size, bool normalized, int byte_offset, bool for_vertex); - Vector<double> _decode_accessor(GLTFState &state, int p_accessor, bool p_for_vertex); - PoolVector<float> _decode_accessor_as_floats(GLTFState &state, int p_accessor, bool p_for_vertex); - PoolVector<int> _decode_accessor_as_ints(GLTFState &state, int p_accessor, bool p_for_vertex); - PoolVector<Vector2> _decode_accessor_as_vec2(GLTFState &state, int p_accessor, bool p_for_vertex); - PoolVector<Vector3> _decode_accessor_as_vec3(GLTFState &state, int p_accessor, bool p_for_vertex); - PoolVector<Color> _decode_accessor_as_color(GLTFState &state, int p_accessor, bool p_for_vertex); - Vector<Quat> _decode_accessor_as_quat(GLTFState &state, int p_accessor, bool p_for_vertex); - Vector<Transform2D> _decode_accessor_as_xform2d(GLTFState &state, int p_accessor, bool p_for_vertex); - Vector<Basis> _decode_accessor_as_basis(GLTFState &state, int p_accessor, bool p_for_vertex); - Vector<Transform> _decode_accessor_as_xform(GLTFState &state, int p_accessor, bool p_for_vertex); - - void _reparent_skeleton(GLTFState &state, int p_node, Vector<Skeleton *> &skeletons, Node *p_parent_node); - void _generate_bone(GLTFState &state, int p_node, Vector<Skeleton *> &skeletons, Node *p_parent_node); - void _generate_node(GLTFState &state, int p_node, Node *p_parent, Node *p_owner, Vector<Skeleton *> &skeletons); - void _import_animation(GLTFState &state, AnimationPlayer *ap, int index, int bake_fps, Vector<Skeleton *> skeletons); - - Spatial *_generate_scene(GLTFState &state, int p_bake_fps); + Error _decode_buffer_view(GLTFState &state, double *dst, const GLTFBufferViewIndex p_buffer_view, const int skip_every, const int skip_bytes, const int element_size, const int count, const GLTFType type, const int component_count, const int component_type, const int component_size, const bool normalized, const int byte_offset, const bool for_vertex); + + Vector<double> _decode_accessor(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + PoolVector<float> _decode_accessor_as_floats(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + PoolVector<int> _decode_accessor_as_ints(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + PoolVector<Vector2> _decode_accessor_as_vec2(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + PoolVector<Vector3> _decode_accessor_as_vec3(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + PoolVector<Color> _decode_accessor_as_color(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + Vector<Quat> _decode_accessor_as_quat(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + Vector<Transform2D> _decode_accessor_as_xform2d(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + Vector<Basis> _decode_accessor_as_basis(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + Vector<Transform> _decode_accessor_as_xform(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); Error _parse_meshes(GLTFState &state); Error _parse_images(GLTFState &state, const String &p_base_path); @@ -323,16 +364,46 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { Error _parse_materials(GLTFState &state); + GLTFNodeIndex _find_highest_node(GLTFState &state, const Vector<GLTFNodeIndex> &subset); + + bool _capture_nodes_in_skin(GLTFState &state, GLTFSkin &skin, const GLTFNodeIndex node_index); + void _capture_nodes_for_multirooted_skin(GLTFState &state, GLTFSkin &skin); + Error _expand_skin(GLTFState &state, GLTFSkin &skin); + Error _verify_skin(GLTFState &state, GLTFSkin &skin); Error _parse_skins(GLTFState &state); + Error _determine_skeletons(GLTFState &state); + Error _reparent_non_joint_skeleton_subtrees(GLTFState &state, GLTFSkeleton &skeleton, const Vector<GLTFNodeIndex> &non_joints); + Error _reparent_to_fake_joint(GLTFState &state, GLTFSkeleton &skeleton, const GLTFNodeIndex node_index); + Error _determine_skeleton_roots(GLTFState &state, const GLTFSkeletonIndex skel_i); + + Error _create_skeletons(GLTFState &state); + Error _map_skin_joints_indices_to_skeleton_bone_indices(GLTFState &state); + + Error _create_skins(GLTFState &state); + bool _skins_are_same(const Ref<Skin> &skin_a, const Ref<Skin> &skin_b); + void _remove_duplicate_skins(GLTFState &state); + Error _parse_cameras(GLTFState &state); Error _parse_animations(GLTFState &state); + BoneAttachment *_generate_bone_attachment(GLTFState &state, Skeleton *skeleton, const GLTFNodeIndex node_index); + MeshInstance *_generate_mesh_instance(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); + Camera *_generate_camera(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); + Spatial *_generate_spatial(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); + + void _generate_scene_node(GLTFState &state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index); + Spatial *_generate_scene(GLTFState &state, const int p_bake_fps); + + void _process_mesh_instances(GLTFState &state, Spatial *scene_root); + void _assign_scene_names(GLTFState &state); template <class T> - T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, GLTFAnimation::Interpolation p_interp); + T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp); + + void _import_animation(GLTFState &state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps); public: virtual uint32_t get_import_flags() const; diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 64994e21b0..b111750992 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -1178,7 +1178,7 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 15)); r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "animation/filter_script", PROPERTY_HINT_MULTILINE_TEXT), "")); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.anim),Files (.tres)"), animations_out ? true : false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.anim),Files (.tres)"), animations_out)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/keep_custom_tracks"), animations_out)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/optimizer/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/optimizer/max_linear_error"), 0.05)); diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index 6918fe7977..1d72e370b3 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -132,6 +132,7 @@ void ImportDock::set_edit_path(const String &p_path) { params->paths.push_back(p_path); import->set_disabled(false); import_as->set_disabled(false); + preset->set_disabled(false); imported->set_text(p_path.get_file()); } @@ -287,6 +288,7 @@ void ImportDock::set_edit_multiple_paths(const Vector<String> &p_paths) { params->paths = p_paths; import->set_disabled(false); import_as->set_disabled(false); + preset->set_disabled(false); imported->set_text(itos(p_paths.size()) + TTR(" Files")); } @@ -367,6 +369,7 @@ void ImportDock::clear() { import->set_disabled(true); import_as->clear(); import_as->set_disabled(true); + preset->set_disabled(true); params->values.clear(); params->properties.clear(); params->update(); @@ -528,11 +531,13 @@ ImportDock::ImportDock() { HBoxContainer *hb = memnew(HBoxContainer); add_margin_child(TTR("Import As:"), hb); import_as = memnew(OptionButton); + import_as->set_disabled(true); import_as->connect("item_selected", this, "_importer_selected"); hb->add_child(import_as); import_as->set_h_size_flags(SIZE_EXPAND_FILL); preset = memnew(MenuButton); - preset->set_text(TTR("Preset...")); + preset->set_text(TTR("Preset")); + preset->set_disabled(true); preset->get_popup()->connect("index_pressed", this, "_preset_selected"); hb->add_child(preset); @@ -545,6 +550,7 @@ ImportDock::ImportDock() { add_child(hb); import = memnew(Button); import->set_text(TTR("Reimport")); + import->set_disabled(true); import->connect("pressed", this, "_reimport_attempt"); hb->add_spacer(); hb->add_child(import); diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index 8ba7d9fba7..02b0159241 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -253,13 +253,11 @@ void InspectorDock::_prepare_history() { text = obj->get_class(); } - if (i == editor_history->get_history_pos()) { + if (i == editor_history->get_history_pos() && current) { text = "[" + text + "]"; } history_menu->get_popup()->add_icon_item(icon, text, i); } - - editor_path->update_path(); } void InspectorDock::_select_history(int p_idx) const { @@ -296,7 +294,7 @@ void InspectorDock::_edit_forward() { } void InspectorDock::_edit_back() { EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history(); - if (editor_history->previous() || editor_history->get_path_size() == 1) + if ((current && editor_history->previous()) || editor_history->get_path_size() == 1) editor->edit_current(); } @@ -408,6 +406,7 @@ void InspectorDock::update(Object *p_object) { warning->hide(); search->set_editable(false); + editor_path->set_disabled(true); editor_path->set_text(""); editor_path->set_tooltip(""); editor_path->set_icon(NULL); @@ -420,6 +419,7 @@ void InspectorDock::update(Object *p_object) { object_menu->set_disabled(false); search->set_editable(true); + editor_path->set_disabled(false); resource_save_button->set_disabled(!is_resource); PopupMenu *p = object_menu->get_popup(); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 173079b6de..d7451849a1 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -38,7 +38,7 @@ #include "editor/animation_track_editor.h" #include "editor/editor_settings.h" -// For onion skinning +// For onion skinning. #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" #include "scene/main/viewport.h" @@ -690,8 +690,10 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) { if (p_state.has("animation")) { String anim = p_state["animation"]; - _select_anim_by_name(anim); - _animation_edit(); + if (!anim.empty() && player->has_animation(anim)) { + _select_anim_by_name(anim); + _animation_edit(); + } } } } @@ -1088,20 +1090,6 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag) EditorNode::get_singleton()->get_inspector()->refresh(); } -void AnimationPlayerEditor::_hide_anim_editors() { - - player = NULL; - hide(); - set_process(false); - - track_editor->set_animation(Ref<Animation>()); - track_editor->set_root(NULL); - track_editor->show_select_node_warning(true); -} - -void AnimationPlayerEditor::_animation_about_to_show_menu() { -} - void AnimationPlayerEditor::_animation_tool_menu(int p_option) { String current; @@ -1489,7 +1477,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { player->seek(cpos, false); player->restore_animated_values(values_backup); - // Restor state of main editors. + // Restore state of main editors. if (SpatialEditor::get_singleton()->is_visible()) { // 3D SpatialEditor::get_singleton()->set_state(spatial_edit_state); @@ -1519,7 +1507,7 @@ void AnimationPlayerEditor::_stop_onion_skinning() { _free_onion_layers(); - // Clean up the overlay + // Clean up the overlay. onion.can_overlay = false; plugin->update_overlays(); } @@ -1557,7 +1545,6 @@ void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_list_changed"), &AnimationPlayerEditor::_list_changed); ClassDB::bind_method(D_METHOD("_animation_key_editor_seek"), &AnimationPlayerEditor::_animation_key_editor_seek); ClassDB::bind_method(D_METHOD("_animation_key_editor_anim_len_changed"), &AnimationPlayerEditor::_animation_key_editor_anim_len_changed); - ClassDB::bind_method(D_METHOD("_hide_anim_editors"), &AnimationPlayerEditor::_hide_anim_editors); ClassDB::bind_method(D_METHOD("_animation_duplicate"), &AnimationPlayerEditor::_animation_duplicate); ClassDB::bind_method(D_METHOD("_blend_editor_next_changed"), &AnimationPlayerEditor::_blend_editor_next_changed); ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &AnimationPlayerEditor::_unhandled_key_input); @@ -1776,7 +1763,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay _update_player(); - // Onion skinning + // Onion skinning. track_editor->connect("visibility_changed", this, "_editor_visibility_changed"); diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 4ad30675ec..eed7344395 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -130,9 +130,9 @@ class AnimationPlayerEditor : public VBoxContainer { AnimationTrackEditor *track_editor; - // Onion skinning + // Onion skinning. struct { - // Settings + // Settings. bool enabled; bool past; bool future; @@ -142,11 +142,11 @@ class AnimationPlayerEditor : public VBoxContainer { bool include_gizmos; int get_needed_capture_count() const { - // 'Differences only' needs a capture of the present + // 'Differences only' needs a capture of the present. return (past && future ? 2 * steps : steps) + (differences_only ? 1 : 0); } - // Rendering + // Rendering. int64_t last_frame; int can_overlay; Size2 capture_size; @@ -195,8 +195,6 @@ class AnimationPlayerEditor : public VBoxContainer { void _update_player(); void _blend_edited(); - void _hide_anim_editors(); - void _animation_player_changed(Object *p_pl); void _animation_key_editor_seek(float p_pos, bool p_drag); @@ -205,7 +203,6 @@ class AnimationPlayerEditor : public VBoxContainer { void _unhandled_key_input(const Ref<InputEvent> &p_ev); void _animation_tool_menu(int p_option); void _onion_skinning_menu(int p_option); - void _animation_about_to_show_menu(); void _editor_visibility_changed(); bool _are_onion_layers_valid(); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index e4cd71fec0..625ca5e603 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1967,6 +1967,11 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { if (key_auto_insert_button->is_pressed()) { _insert_animation_keys(true, false, false, true); } + + //Make sure smart snapping lines disappear. + snap_target[0] = SNAP_TARGET_NONE; + snap_target[1] = SNAP_TARGET_NONE; + drag_type = DRAG_NONE; viewport->update(); return true; @@ -1975,6 +1980,8 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { // Cancel a drag if (b.is_valid() && b->get_button_index() == BUTTON_RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection, true); + snap_target[0] = SNAP_TARGET_NONE; + snap_target[1] = SNAP_TARGET_NONE; drag_type = DRAG_NONE; viewport->update(); return true; @@ -3678,7 +3685,6 @@ void CanvasItemEditor::_notification(int p_what) { key_auto_insert_button->set_icon(get_icon("AutoKey", "EditorIcons")); zoom_minus->set_icon(get_icon("ZoomLess", "EditorIcons")); - zoom_reset->set_icon(get_icon("ZoomReset", "EditorIcons")); zoom_plus->set_icon(get_icon("ZoomMore", "EditorIcons")); presets_menu->set_icon(get_icon("ControlLayout", "EditorIcons")); @@ -4036,9 +4042,22 @@ void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) { view_offset.x = Math::round(view_offset.x + ofs.x); view_offset.y = Math::round(view_offset.y + ofs.y); + _update_zoom_label(); update_viewport(); } +void CanvasItemEditor::_update_zoom_label() { + String zoom_text; + if (zoom >= 10) { + // Don't show a decimal when the zoom level is higher than 1000 % + zoom_text = rtos(Math::round(zoom * 100)) + " %"; + } else { + zoom_text = rtos(Math::stepify(zoom * 100, 0.1)) + " %"; + } + + zoom_reset->set_text(zoom_text); +} + void CanvasItemEditor::_button_zoom_minus() { _zoom_on_position(zoom / Math_SQRT2, viewport_scrollable->get_size() / 2.0); } @@ -4811,6 +4830,7 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { Dictionary state = p_state; if (state.has("zoom")) { zoom = p_state["zoom"]; + _update_zoom_label(); } if (state.has("ofs")) { @@ -5031,6 +5051,8 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { snap_rotation = false; snap_relative = false; snap_pixel = false; + snap_target[0] = SNAP_TARGET_NONE; + snap_target[1] = SNAP_TARGET_NONE; anchors_mode = false; @@ -5128,6 +5150,8 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { zoom_hb = memnew(HBoxContainer); viewport->add_child(zoom_hb); zoom_hb->set_begin(Point2(5, 5)); + // Bring the zoom percentage closer to the zoom buttons + zoom_hb->add_constant_override("separation", Math::round(-8 * EDSCALE)); zoom_minus = memnew(ToolButton); zoom_hb->add_child(zoom_minus); @@ -5140,6 +5164,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { zoom_reset->connect("pressed", this, "_button_zoom_reset"); zoom_reset->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_reset", TTR("Zoom Reset"), KEY_MASK_CMD | KEY_0)); zoom_reset->set_focus_mode(FOCUS_NONE); + zoom_reset->set_text_align(Button::TextAlign::ALIGN_CENTER); + // Prevent the button's size from changing when the text size changes + zoom_reset->set_custom_minimum_size(Size2(75 * EDSCALE, 0)); zoom_plus = memnew(ToolButton); zoom_hb->add_child(zoom_plus); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 4e030c63da..6cce1ca10e 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -525,6 +525,7 @@ private: HBoxContainer *zoom_hb; void _zoom_on_position(float p_zoom, Point2 p_position = Point2()); + void _update_zoom_label(); void _button_zoom_minus(); void _button_zoom_reset(); void _button_zoom_plus(); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 413843d536..4c22138a20 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -538,6 +538,14 @@ void ScriptEditor::_open_recent_script(int p_idx) { // if it's a path then it's most likely a deleted file not help } else if (path.find("::") != -1) { // built-in script + String res_path = path.get_slice("::", 0); + if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { + if (!EditorNode::get_singleton()->is_scene_open(res_path)) { + EditorNode::get_singleton()->load_scene(res_path); + } + } else { + EditorNode::get_singleton()->load_resource(res_path); + } Ref<Script> script = ResourceLoader::load(path); if (script.is_valid()) { edit(script, true); @@ -1024,12 +1032,16 @@ void ScriptEditor::_menu_option(int p_option) { if (extensions.find(path.get_extension()) || built_in) { if (built_in) { - String scene_path = path.get_slice("::", 0); - if (!EditorNode::get_singleton()->is_scene_open(scene_path)) { - EditorNode::get_singleton()->load_scene(scene_path); - script_editor->call_deferred("_menu_option", p_option); - previous_scripts.push_back(path); //repeat the operation - return; + String res_path = path.get_slice("::", 0); + if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { + if (!EditorNode::get_singleton()->is_scene_open(res_path)) { + EditorNode::get_singleton()->load_scene(res_path); + script_editor->call_deferred("_menu_option", p_option); + previous_scripts.push_back(path); //repeat the operation + return; + } + } else { + EditorNode::get_singleton()->load_resource(res_path); } } @@ -3296,8 +3308,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { debug_menu->set_text(TTR("Debug")); debug_menu->set_switch_on_hover(true); debug_menu->get_popup()->set_hide_on_window_lose_focus(true); - debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/step_over", TTR("Step Over"), KEY_F10), DEBUG_NEXT); debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/step_into", TTR("Step Into"), KEY_F11), DEBUG_STEP); + debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/step_over", TTR("Step Over"), KEY_F10), DEBUG_NEXT); debug_menu->get_popup()->add_separator(); debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/break", TTR("Break")), DEBUG_BREAK); debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/continue", TTR("Continue"), KEY_F12), DEBUG_CONTINUE); @@ -3459,15 +3471,18 @@ void ScriptEditorPlugin::edit(Object *p_object) { if (Object::cast_to<Script>(p_object)) { Script *p_script = Object::cast_to<Script>(p_object); - String scene_path = p_script->get_path().get_slice("::", 0); + String res_path = p_script->get_path().get_slice("::", 0); - if (_is_built_in_script(p_script) && !EditorNode::get_singleton()->is_scene_open(scene_path)) { - EditorNode::get_singleton()->load_scene(scene_path); - - script_editor->call_deferred("edit", p_script); - } else { - script_editor->edit(p_script); + if (_is_built_in_script(p_script)) { + if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { + if (!EditorNode::get_singleton()->is_scene_open(res_path)) { + EditorNode::get_singleton()->load_scene(res_path); + } + } else { + EditorNode::get_singleton()->load_resource(res_path); + } } + script_editor->edit(p_script); } else if (Object::cast_to<TextFile>(p_object)) { script_editor->edit(Object::cast_to<TextFile>(p_object)); } diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 3b300a7ad5..073e6f74e9 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1532,93 +1532,98 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { Ref<InputEventMouseButton> mb = ev; + Ref<InputEventKey> k = ev; + Point2 local_pos; + bool create_menu = false; - if (mb.is_valid()) { - - if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) { - int col, row; - TextEdit *tx = code_editor->get_text_edit(); - tx->_get_mouse_pos(mb->get_global_position() - tx->get_global_position(), row, col); - Vector2 mpos = mb->get_global_position() - tx->get_global_position(); - - tx->set_right_click_moves_caret(EditorSettings::get_singleton()->get("text_editor/cursor/right_click_moves_caret")); - if (tx->is_right_click_moving_caret()) { - if (tx->is_selection_active()) { + TextEdit *tx = code_editor->get_text_edit(); + if (mb.is_valid() && mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) { + local_pos = mb->get_global_position() - tx->get_global_position(); + create_menu = true; + } else if (k.is_valid() && k->get_scancode() == KEY_MENU) { + local_pos = tx->_get_cursor_pixel_pos(); + create_menu = true; + } - int from_line = tx->get_selection_from_line(); - int to_line = tx->get_selection_to_line(); - int from_column = tx->get_selection_from_column(); - int to_column = tx->get_selection_to_column(); + if (create_menu) { + int col, row; + tx->_get_mouse_pos(local_pos, row, col); - if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { - // Right click is outside the selected text - tx->deselect(); - } - } - if (!tx->is_selection_active()) { - tx->cursor_set_line(row, true, false); - tx->cursor_set_column(col); + tx->set_right_click_moves_caret(EditorSettings::get_singleton()->get("text_editor/cursor/right_click_moves_caret")); + if (tx->is_right_click_moving_caret()) { + if (tx->is_selection_active()) { + int from_line = tx->get_selection_from_line(); + int to_line = tx->get_selection_to_line(); + int from_column = tx->get_selection_from_column(); + int to_column = tx->get_selection_to_column(); + + if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { + // Right click is outside the selected text + tx->deselect(); } } + if (!tx->is_selection_active()) { + tx->cursor_set_line(row, true, false); + tx->cursor_set_column(col); + } + } - String word_at_mouse = tx->get_word_at_pos(mpos); - if (word_at_mouse == "") - word_at_mouse = tx->get_word_under_cursor(); - if (word_at_mouse == "") - word_at_mouse = tx->get_selection_text(); + String word_at_pos = tx->get_word_at_pos(local_pos); + if (word_at_pos == "") + word_at_pos = tx->get_word_under_cursor(); + if (word_at_pos == "") + word_at_pos = tx->get_selection_text(); - bool has_color = (word_at_mouse == "Color"); - bool foldable = tx->can_fold(row) || tx->is_folded(row); - bool open_docs = false; - bool goto_definition = false; + bool has_color = (word_at_pos == "Color"); + bool foldable = tx->can_fold(row) || tx->is_folded(row); + bool open_docs = false; + bool goto_definition = false; - if (word_at_mouse.is_resource_file()) { + if (word_at_pos.is_resource_file()) { + open_docs = true; + } else { + Node *base = get_tree()->get_edited_scene_root(); + if (base) { + base = _find_node_for_script(base, base, script); + } + ScriptLanguage::LookupResult result; + if (script->get_language()->lookup_code(code_editor->get_text_edit()->get_text_for_lookup_completion(), word_at_pos, script->get_path(), base, result) == OK) { open_docs = true; - } else { - - Node *base = get_tree()->get_edited_scene_root(); - if (base) { - base = _find_node_for_script(base, base, script); - } - ScriptLanguage::LookupResult result; - if (script->get_language()->lookup_code(code_editor->get_text_edit()->get_text_for_lookup_completion(), word_at_mouse, script->get_path(), base, result) == OK) { - open_docs = true; - } } + } - if (has_color) { - String line = tx->get_line(row); - color_position.x = row; - color_position.y = col; - - int begin = 0; - int end = 0; - bool valid = false; - for (int i = col; i < line.length(); i++) { - if (line[i] == '(') { - begin = i; - continue; - } else if (line[i] == ')') { - end = i + 1; - valid = true; - break; - } + if (has_color) { + String line = tx->get_line(row); + color_position.x = row; + color_position.y = col; + + int begin = 0; + int end = 0; + bool valid = false; + for (int i = col; i < line.length(); i++) { + if (line[i] == '(') { + begin = i; + continue; + } else if (line[i] == ')') { + end = i + 1; + valid = true; + break; } - if (valid) { - color_args = line.substr(begin, end - begin); - String stripped = color_args.replace(" ", "").replace("(", "").replace(")", ""); - Vector<float> color = stripped.split_floats(","); - if (color.size() > 2) { - float alpha = color.size() > 3 ? color[3] : 1.0f; - color_picker->set_pick_color(Color(color[0], color[1], color[2], alpha)); - } - color_panel->set_position(get_global_transform().xform(get_local_mouse_position())); - } else { - has_color = false; + } + if (valid) { + color_args = line.substr(begin, end - begin); + String stripped = color_args.replace(" ", "").replace("(", "").replace(")", ""); + Vector<float> color = stripped.split_floats(","); + if (color.size() > 2) { + float alpha = color.size() > 3 ? color[3] : 1.0f; + color_picker->set_pick_color(Color(color[0], color[1], color[2], alpha)); } + color_panel->set_position(get_global_transform().xform(local_pos)); + } else { + has_color = false; } - _make_context_menu(tx->is_selection_active(), has_color, foldable, open_docs, goto_definition); } + _make_context_menu(tx->is_selection_active(), has_color, foldable, open_docs, goto_definition, local_pos); } } @@ -1643,7 +1648,7 @@ void ScriptTextEditor::_color_changed(const Color &p_color) { code_editor->get_text_edit()->update(); } -void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition) { +void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos) { context_menu->clear(); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO); @@ -1680,7 +1685,7 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p context_menu->add_item(TTR("Pick Color"), EDIT_PICK_COLOR); } - context_menu->set_position(get_global_transform().xform(get_local_mouse_position())); + context_menu->set_position(get_global_transform().xform(p_pos)); context_menu->set_size(Vector2(1, 1)); context_menu->popup(); } diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 38c6da5c33..0ea8726ecc 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -169,7 +169,7 @@ protected: void _edit_option(int p_op); void _edit_option_toggle_inline_comment(); - void _make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition); + void _make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos); void _text_edit_gui_input(const Ref<InputEvent> &ev); void _color_changed(const Color &p_color); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index e81c97d5dd..df3e23a9e9 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -523,9 +523,16 @@ void ShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { tx->cursor_set_column(col); } } - _make_context_menu(tx->is_selection_active()); + _make_context_menu(tx->is_selection_active(), get_local_mouse_position()); } } + + Ref<InputEventKey> k = ev; + if (k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_MENU) { + TextEdit *tx = shader_editor->get_text_edit(); + _make_context_menu(tx->is_selection_active(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->_get_cursor_pixel_pos())); + context_menu->grab_focus(); + } } void ShaderEditor::_update_bookmark_list() { @@ -565,7 +572,7 @@ void ShaderEditor::_bookmark_item_pressed(int p_idx) { } } -void ShaderEditor::_make_context_menu(bool p_selection) { +void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) { context_menu->clear(); if (p_selection) { @@ -585,7 +592,7 @@ void ShaderEditor::_make_context_menu(bool p_selection) { context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); - context_menu->set_position(get_global_transform().xform(get_local_mouse_position())); + context_menu->set_position(get_global_transform().xform(p_position)); context_menu->set_size(Vector2(1, 1)); context_menu->popup(); } diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 8d3f4d4fe8..ca9f489713 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -122,7 +122,7 @@ class ShaderEditor : public PanelContainer { protected: void _notification(int p_what); static void _bind_methods(); - void _make_context_menu(bool p_selection); + void _make_context_menu(bool p_selection, Vector2 p_position); void _text_edit_gui_input(const Ref<InputEvent> &ev); void _update_bookmark_list(); diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index ecc631d045..18049e62b4 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -5186,7 +5186,7 @@ void SpatialEditor::snap_selected_nodes_to_floor() { // The maximum height an object can travel to be snapped const float max_snap_height = 20.0; - // Will be set to `true` if at least one node from the selection was sucessfully snapped + // Will be set to `true` if at least one node from the selection was successfully snapped bool snapped_to_floor = false; if (keys.size()) { diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h index 728b67f6fa..aa61f4bae1 100644 --- a/editor/plugins/spatial_editor_plugin.h +++ b/editor/plugins/spatial_editor_plugin.h @@ -58,6 +58,7 @@ public: RID instance; Ref<ArrayMesh> mesh; Ref<Material> material; + Ref<SkinReference> skin_reference; RID skeleton; bool billboard; bool unscaled; @@ -101,7 +102,7 @@ protected: public: void add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard = false); - void add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard = false, const RID &p_skeleton = RID(), const Ref<Material> &p_material = Ref<Material>()); + void add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard = false, const Ref<SkinReference> &p_skin_reference = Ref<SkinReference>(), const Ref<Material> &p_material = Ref<Material>()); void add_collision_segments(const Vector<Vector3> &p_lines); void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh); void add_unscaled_billboard(const Ref<Material> &p_material, float p_scale = 1); diff --git a/editor/plugins/sprite_editor_plugin.cpp b/editor/plugins/sprite_editor_plugin.cpp index 2deb2090e2..69fd592652 100644 --- a/editor/plugins/sprite_editor_plugin.cpp +++ b/editor/plugins/sprite_editor_plugin.cpp @@ -102,7 +102,7 @@ Vector<Vector2> expand(const Vector<Vector2> &points, const Rect2i &rect, float int lasti = p2->Contour.size() - 1; Vector2 prev = Vector2(p2->Contour[lasti].X / PRECISION, p2->Contour[lasti].Y / PRECISION); - for (unsigned int i = 0; i < p2->Contour.size(); i++) { + for (uint64_t i = 0; i < p2->Contour.size(); i++) { Vector2 cur = Vector2(p2->Contour[i].X / PRECISION, p2->Contour[i].Y / PRECISION); if (cur.distance_to(prev) > 0.5) { diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index d91de6cbf6..394122d91d 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -464,9 +464,11 @@ void SpriteFramesEditor::_animation_select() { if (updating) return; - double value = anim_speed->get_line_edit()->get_text().to_double(); - if (!Math::is_equal_approx(value, frames->get_animation_speed(edited_anim))) - _animation_fps_changed(value); + if (frames->has_animation(edited_anim)) { + double value = anim_speed->get_line_edit()->get_text().to_double(); + if (!Math::is_equal_approx(value, frames->get_animation_speed(edited_anim))) + _animation_fps_changed(value); + } TreeItem *selected = animations->get_selected(); ERR_FAIL_COND(!selected); @@ -548,6 +550,7 @@ void SpriteFramesEditor::_animation_name_edited() { undo_redo->commit_action(); } + void SpriteFramesEditor::_animation_add() { String name = "New Anim"; @@ -578,13 +581,21 @@ void SpriteFramesEditor::_animation_add() { undo_redo->commit_action(); animations->grab_focus(); } + void SpriteFramesEditor::_animation_remove() { + if (updating) return; if (!frames->has_animation(edited_anim)) return; + delete_dialog->set_text(TTR("Delete Animation?")); + delete_dialog->popup_centered_minsize(); +} + +void SpriteFramesEditor::_animation_remove_confirmed() { + undo_redo->create_action(TTR("Remove Animation")); undo_redo->add_do_method(frames, "remove_animation", edited_anim); undo_redo->add_undo_method(frames, "add_animation", edited_anim); @@ -598,6 +609,8 @@ void SpriteFramesEditor::_animation_remove() { undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); + edited_anim = StringName(); + undo_redo->commit_action(); } @@ -743,7 +756,9 @@ Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_f if (frame.is_null()) return Variant(); - return EditorNode::get_singleton()->drag_resource(frame, p_from); + Dictionary drag_data = EditorNode::get_singleton()->drag_resource(frame, p_from); + drag_data["frame"] = idx; // store the frame, incase we want to reorder frames inside 'drop_data_fw' + return drag_data; } bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { @@ -753,8 +768,9 @@ bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant & if (!d.has("type")) return false; + // reordering frames if (d.has("from") && (Object *)(d["from"]) == tree) - return false; + return true; if (String(d["type"]) == "resource" && d.has("resource")) { RES r = d["resource"]; @@ -806,13 +822,31 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da Ref<Texture> texture = r; if (texture.is_valid()) { - - undo_redo->create_action(TTR("Add Frame")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, at_pos == -1 ? -1 : at_pos); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos); - undo_redo->add_do_method(this, "_update_library"); - undo_redo->add_undo_method(this, "_update_library"); - undo_redo->commit_action(); + bool reorder = false; + if (d.has("from") && (Object *)(d["from"]) == tree) + reorder = true; + + if (reorder) { //drop is from reordering frames + int from_frame = -1; + if (d.has("frame")) + from_frame = d["frame"]; + + undo_redo->create_action(TTR("Move Frame")); + undo_redo->add_do_method(frames, "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame); + undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, at_pos == -1 ? -1 : at_pos); + undo_redo->add_undo_method(frames, "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos); + undo_redo->add_undo_method(frames, "add_frame", edited_anim, texture, from_frame); + undo_redo->add_do_method(this, "_update_library"); + undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); + } else { + undo_redo->create_action(TTR("Add Frame")); + undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, at_pos == -1 ? -1 : at_pos); + undo_redo->add_undo_method(frames, "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos); + undo_redo->add_do_method(this, "_update_library"); + undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); + } } } @@ -840,6 +874,7 @@ void SpriteFramesEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_animation_name_edited"), &SpriteFramesEditor::_animation_name_edited); ClassDB::bind_method(D_METHOD("_animation_add"), &SpriteFramesEditor::_animation_add); ClassDB::bind_method(D_METHOD("_animation_remove"), &SpriteFramesEditor::_animation_remove); + ClassDB::bind_method(D_METHOD("_animation_remove_confirmed"), &SpriteFramesEditor::_animation_remove_confirmed); ClassDB::bind_method(D_METHOD("_animation_loop_changed"), &SpriteFramesEditor::_animation_loop_changed); ClassDB::bind_method(D_METHOD("_animation_fps_changed"), &SpriteFramesEditor::_animation_fps_changed); ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &SpriteFramesEditor::get_drag_data_fw); @@ -870,7 +905,6 @@ SpriteFramesEditor::SpriteFramesEditor() { new_anim = memnew(ToolButton); new_anim->set_tooltip(TTR("New Animation")); hbc_animlist->add_child(new_anim); - new_anim->set_h_size_flags(SIZE_EXPAND_FILL); new_anim->connect("pressed", this, "_animation_add"); remove_anim = memnew(ToolButton); @@ -926,7 +960,7 @@ SpriteFramesEditor::SpriteFramesEditor() { paste->set_tooltip(TTR("Paste")); hbc->add_child(paste); - hbc->add_spacer(false); + hbc->add_child(memnew(VSeparator)); empty = memnew(ToolButton); empty->set_tooltip(TTR("Insert Empty (Before)")); @@ -987,6 +1021,10 @@ SpriteFramesEditor::SpriteFramesEditor() { edited_anim = "default"; + delete_dialog = memnew(ConfirmationDialog); + add_child(delete_dialog); + delete_dialog->connect("confirmed", this, "_animation_remove_confirmed"); + split_sheet_dialog = memnew(ConfirmationDialog); add_child(split_sheet_dialog); VBoxContainer *split_sheet_vb = memnew(VBoxContainer); diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index d64431cde7..f20b54f910 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -73,6 +73,8 @@ class SpriteFramesEditor : public HSplitContainer { StringName edited_anim; + ConfirmationDialog *delete_dialog; + ConfirmationDialog *split_sheet_dialog; ScrollContainer *splite_sheet_scroll; TextureRect *split_sheet_preview; @@ -98,6 +100,7 @@ class SpriteFramesEditor : public HSplitContainer { void _animation_name_edited(); void _animation_add(); void _animation_remove(); + void _animation_remove_confirmed(); void _animation_loop_changed(); void _animation_fps_changed(double p_value); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 89e419ede8..d0320bcd8b 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -30,6 +30,7 @@ #include "text_editor.h" +#include "core/os/keyboard.h" #include "editor_node.h" void TextEditor::add_syntax_highlighter(SyntaxHighlighter *p_highlighter) { @@ -577,13 +578,21 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { } if (!mb->is_pressed()) { - _make_context_menu(tx->is_selection_active(), can_fold, is_folded); + _make_context_menu(tx->is_selection_active(), can_fold, is_folded, get_local_mouse_position()); } } } + + Ref<InputEventKey> k = ev; + if (k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_MENU) { + TextEdit *tx = code_editor->get_text_edit(); + int line = tx->cursor_get_line(); + _make_context_menu(tx->is_selection_active(), tx->can_fold(line), tx->is_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->_get_cursor_pixel_pos())); + context_menu->grab_focus(); + } } -void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded) { +void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position) { context_menu->clear(); if (p_selection) { @@ -609,7 +618,7 @@ void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is if (p_can_fold || p_is_folded) context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); - context_menu->set_position(get_global_transform().xform(get_local_mouse_position())); + context_menu->set_position(get_global_transform().xform(p_position)); context_menu->set_size(Vector2(1, 1)); context_menu->popup(); } diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index c8b49a61e0..7d441a187d 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -101,7 +101,7 @@ protected: void _notification(int p_what); void _edit_option(int p_op); - void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded); + 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); Map<String, SyntaxHighlighter *> highlighters; diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 4d349f06b7..21eebf9ca2 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -625,9 +625,12 @@ void TextureRegionEditor::_update_rect() { rect = node_sprite->get_region_rect(); else if (node_sprite_3d) rect = node_sprite_3d->get_region_rect(); - else if (node_ninepatch) + else if (node_ninepatch) { rect = node_ninepatch->get_region_rect(); - else if (obj_styleBox.is_valid()) + if (rect == Rect2()) { + rect = Rect2(Vector2(), node_ninepatch->get_texture()->get_size()); + } + } else if (obj_styleBox.is_valid()) rect = obj_styleBox->get_region_rect(); else if (atlas_tex.is_valid()) rect = atlas_tex->get_region(); diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp index fb3fd93f97..86d538e702 100644 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ b/editor/plugins/tile_map_editor_plugin.cpp @@ -376,7 +376,7 @@ void TileMapEditor::_sbox_input(const Ref<InputEvent> &p_ie) { } // Implementation detail of TileMapEditor::_update_palette(); -// in modern C++ this could have been inside its body +// In modern C++ this could have been inside its body. namespace { struct _PaletteEntry { int id; @@ -393,10 +393,10 @@ void TileMapEditor::_update_palette() { if (!node) return; - // Update the clear button + // Update the clear button. clear_transform_button->set_disabled(!flip_h && !flip_v && !transpose); - // Update the palette + // Update the palette. Vector<int> selected = get_selected_tiles(); int selected_single = palette->get_current(); int selected_manual = manual_palette->get_current(); @@ -405,8 +405,15 @@ void TileMapEditor::_update_palette() { manual_palette->hide(); Ref<TileSet> tileset = node->get_tileset(); - if (tileset.is_null()) + if (tileset.is_null()) { + search_box->set_text(""); + search_box->set_editable(false); + info_message->show(); return; + } + + search_box->set_editable(true); + info_message->hide(); List<int> tiles; tileset->get_tile_list(&tiles); @@ -421,7 +428,6 @@ void TileMapEditor::_update_palette() { bool sort_by_name = bool(EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true)); palette->add_constant_override("hseparation", hseparation * EDSCALE); - palette->add_constant_override("vseparation", 8 * EDSCALE); palette->set_fixed_icon_size(Size2(min_size, min_size)); palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1)); @@ -479,7 +485,7 @@ void TileMapEditor::_update_palette() { region.position += (region.size + Vector2(spacing, spacing)) * tileset->autotile_get_icon_coordinate(entries[i].id); } - // Transpose and flip + // Transpose and flip. palette->set_item_icon_transposed(palette->get_item_count() - 1, transpose); if (flip_h) { region.size.x = -region.size.x; @@ -488,14 +494,14 @@ void TileMapEditor::_update_palette() { region.size.y = -region.size.y; } - // Set region + // Set region. if (region.size != Size2()) palette->set_item_icon_region(palette->get_item_count() - 1, region); - // Set icon + // Set icon. palette->set_item_icon(palette->get_item_count() - 1, tex); - // Modulation + // Modulation. Color color = tileset->tile_get_modulate(entries[i].id); palette->set_item_icon_modulate(palette->get_item_count() - 1, color); } @@ -511,50 +517,47 @@ void TileMapEditor::_update_palette() { palette->select(0); } - if (sel_tile != TileMap::INVALID_CELL) { - if ((manual_autotile && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) || - (!priority_atlastile && tileset->tile_get_tile_mode(sel_tile) == TileSet::ATLAS_TILE)) { + if (sel_tile != TileMap::INVALID_CELL && ((manual_autotile && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) || (!priority_atlastile && tileset->tile_get_tile_mode(sel_tile) == TileSet::ATLAS_TILE))) { - const Map<Vector2, uint32_t> &tiles2 = tileset->autotile_get_bitmask_map(sel_tile); + const Map<Vector2, uint32_t> &tiles2 = tileset->autotile_get_bitmask_map(sel_tile); - Vector<Vector2> entries2; - for (const Map<Vector2, uint32_t>::Element *E = tiles2.front(); E; E = E->next()) { - entries2.push_back(E->key()); + Vector<Vector2> entries2; + for (const Map<Vector2, uint32_t>::Element *E = tiles2.front(); E; E = E->next()) { + entries2.push_back(E->key()); + } + // Sort tiles in row-major order. + struct SwapComparator { + _FORCE_INLINE_ bool operator()(const Vector2 &v_l, const Vector2 &v_r) const { + return v_l.y != v_r.y ? v_l.y < v_r.y : v_l.x < v_r.x; } - // Sort tiles in row-major order - struct SwapComparator { - _FORCE_INLINE_ bool operator()(const Vector2 &v_l, const Vector2 &v_r) const { - return v_l.y != v_r.y ? v_l.y < v_r.y : v_l.x < v_r.x; - } - }; - entries2.sort_custom<SwapComparator>(); - - Ref<Texture> tex = tileset->tile_get_texture(sel_tile); + }; + entries2.sort_custom<SwapComparator>(); - for (int i = 0; i < entries2.size(); i++) { + Ref<Texture> tex = tileset->tile_get_texture(sel_tile); - manual_palette->add_item(String()); + for (int i = 0; i < entries2.size(); i++) { - if (tex.is_valid()) { + manual_palette->add_item(String()); - Rect2 region = tileset->tile_get_region(sel_tile); - int spacing = tileset->autotile_get_spacing(sel_tile); - region.size = tileset->autotile_get_size(sel_tile); // !! - region.position += (region.size + Vector2(spacing, spacing)) * entries2[i]; + if (tex.is_valid()) { - if (!region.has_no_area()) - manual_palette->set_item_icon_region(manual_palette->get_item_count() - 1, region); + Rect2 region = tileset->tile_get_region(sel_tile); + int spacing = tileset->autotile_get_spacing(sel_tile); + region.size = tileset->autotile_get_size(sel_tile); // !! + region.position += (region.size + Vector2(spacing, spacing)) * entries2[i]; - manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex); - } + if (!region.has_no_area()) + manual_palette->set_item_icon_region(manual_palette->get_item_count() - 1, region); - manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries2[i]); + manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex); } + + manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries2[i]); } } if (manual_palette->get_item_count() > 0) { - // Only show the manual palette if at least tile exists in it + // Only show the manual palette if at least tile exists in it. if (selected_manual == -1 || selected_single != palette->get_current()) selected_manual = 0; if (selected_manual < manual_palette->get_item_count()) @@ -1951,6 +1954,7 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { add_child(priority_button); search_box = memnew(LineEdit); + search_box->set_placeholder(TTR("Filter tiles")); search_box->set_h_size_flags(SIZE_EXPAND_FILL); search_box->connect("text_entered", this, "_text_entered"); search_box->connect("text_changed", this, "_text_changed"); @@ -1973,7 +1977,7 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { palette_container->set_custom_minimum_size(Size2(mw, 0)); add_child(palette_container); - // Add tile palette + // Add tile palette. palette = memnew(ItemList); palette->set_h_size_flags(SIZE_EXPAND_FILL); palette->set_v_size_flags(SIZE_EXPAND_FILL); @@ -1981,11 +1985,21 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { palette->set_icon_mode(ItemList::ICON_MODE_TOP); palette->set_max_text_lines(2); palette->set_select_mode(ItemList::SELECT_MULTI); + palette->add_constant_override("vseparation", 8 * EDSCALE); palette->connect("item_selected", this, "_palette_selected"); palette->connect("multi_selected", this, "_palette_multi_selected"); palette_container->add_child(palette); - // Add autotile override palette + // Add message for when no texture is selected. + info_message = memnew(Label); + info_message->set_text(TTR("Give a TileSet resource to this TileMap to use its tiles.")); + info_message->set_valign(Label::VALIGN_CENTER); + info_message->set_align(Label::ALIGN_CENTER); + info_message->set_autowrap(true); + info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); + palette->add_child(info_message); + + // Add autotile override palette. manual_palette = memnew(ItemList); manual_palette->set_h_size_flags(SIZE_EXPAND_FILL); manual_palette->set_v_size_flags(SIZE_EXPAND_FILL); @@ -1995,15 +2009,14 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { manual_palette->hide(); palette_container->add_child(manual_palette); - // Add menu items + // Add menu items. toolbar = memnew(HBoxContainer); toolbar->hide(); CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar); - // Separator toolbar->add_child(memnew(VSeparator)); - // Tools + // Tools. paint_button = memnew(ToolButton); paint_button->set_shortcut(ED_SHORTCUT("tile_map_editor/paint_tile", TTR("Paint Tile"), KEY_P)); paint_button->set_tooltip(TTR("Shift+LMB: Line Draw\nShift+Ctrl+LMB: Rectangle Paint")); @@ -2031,18 +2044,18 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { _update_button_tool(); - // Container to the right of the toolbar + // Container to the right of the toolbar. toolbar_right = memnew(HBoxContainer); toolbar_right->hide(); toolbar_right->set_h_size_flags(SIZE_EXPAND_FILL); toolbar_right->set_alignment(BoxContainer::ALIGN_END); CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar_right); - // Tile position + // Tile position. tile_info = memnew(Label); toolbar_right->add_child(tile_info); - // Menu + // Menu. options = memnew(MenuButton); options->set_text("TileMap"); options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("TileMap", "EditorIcons")); @@ -2136,7 +2149,8 @@ void TileMapEditorPlugin::make_visible(bool p_visible) { tile_map_editor->show(); tile_map_editor->get_toolbar()->show(); tile_map_editor->get_toolbar_right()->show(); - CanvasItemEditor::get_singleton()->set_current_tool(CanvasItemEditor::TOOL_SELECT); //Change to TOOL_SELECT when TileMap node is selected, to prevent accidental movement. + // Change to TOOL_SELECT when TileMap node is selected, to prevent accidental movement. + CanvasItemEditor::get_singleton()->set_current_tool(CanvasItemEditor::TOOL_SELECT); } else { tile_map_editor->hide(); diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h index c841eb1f98..e3d678c2fd 100644 --- a/editor/plugins/tile_map_editor_plugin.h +++ b/editor/plugins/tile_map_editor_plugin.h @@ -82,6 +82,8 @@ class TileMapEditor : public VBoxContainer { ItemList *palette; ItemList *manual_palette; + Label *info_message; + HBoxContainer *toolbar; HBoxContainer *toolbar_right; diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp index 9096a0e0be..e0bf8dfdb2 100644 --- a/editor/plugins/tile_set_editor_plugin.cpp +++ b/editor/plugins/tile_set_editor_plugin.cpp @@ -579,6 +579,14 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { scroll->set_v_size_flags(SIZE_EXPAND_FILL); scroll->set_clip_contents(true); + empty_message = memnew(Label); + empty_message->set_text(TTR("Add or select a texture on the left panel to edit the tiles bound to it.")); + empty_message->set_valign(Label::VALIGN_CENTER); + empty_message->set_align(Label::ALIGN_CENTER); + empty_message->set_autowrap(true); + empty_message->set_v_size_flags(SIZE_EXPAND_FILL); + main_vb->add_child(empty_message); + workspace_container = memnew(Control); scroll->add_child(workspace_container); @@ -627,7 +635,7 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { helper = memnew(TilesetEditorContext(this)); tile_names_visible = false; - // config scale + // Config scale. max_scale = 10.0f; min_scale = 0.1f; scale_ratio = 1.2f; @@ -3123,12 +3131,28 @@ void TileSetEditor::update_workspace_tile_mode() { } tools[SELECT_NEXT]->set_disabled(true); tools[SELECT_PREVIOUS]->set_disabled(true); + + tools[ZOOM_OUT]->hide(); + tools[ZOOM_1]->hide(); + tools[ZOOM_IN]->hide(); + tools[VISIBLE_INFO]->hide(); + + scroll->hide(); + empty_message->show(); } else { for (int i = 1; i < WORKSPACE_MODE_MAX; i++) { tool_workspacemode[i]->set_disabled(false); } tools[SELECT_NEXT]->set_disabled(false); tools[SELECT_PREVIOUS]->set_disabled(false); + + tools[ZOOM_OUT]->show(); + tools[ZOOM_1]->show(); + tools[ZOOM_IN]->show(); + tools[VISIBLE_INFO]->show(); + + scroll->show(); + empty_message->hide(); } if (workspace_mode != WORKSPACE_EDIT) { diff --git a/editor/plugins/tile_set_editor_plugin.h b/editor/plugins/tile_set_editor_plugin.h index fff9ef7731..944dc04e4e 100644 --- a/editor/plugins/tile_set_editor_plugin.h +++ b/editor/plugins/tile_set_editor_plugin.h @@ -138,6 +138,7 @@ class TileSetEditor : public HSplitContainer { int current_item_index; Sprite *preview; ScrollContainer *scroll; + Label *empty_message; Control *workspace_container; bool draw_handles; Control *workspace_overlay; diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index c059977487..e8cd7692b6 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -59,14 +59,6 @@ void VersionControlEditorPlugin::_selected_a_vcs(int p_id) { List<StringName> available_addons = get_available_vcs_names(); const StringName selected_vcs = set_up_choice->get_item_text(p_id); - - if (available_addons.find(selected_vcs) != NULL) { - - set_up_init_button->set_disabled(false); - } else { - - set_up_init_button->set_disabled(true); - } } void VersionControlEditorPlugin::_populate_available_vcs_names() { @@ -75,9 +67,6 @@ void VersionControlEditorPlugin::_populate_available_vcs_names() { if (!called) { - set_up_choice->add_item("Select an available VCS"); - - fetch_available_vcs_addon_names(); List<StringName> available_addons = get_available_vcs_names(); for (int i = 0; i < available_addons.size(); i++) { @@ -95,19 +84,22 @@ VersionControlEditorPlugin *VersionControlEditorPlugin::get_singleton() { void VersionControlEditorPlugin::popup_vcs_set_up_dialog(const Control *p_gui_base) { - Size2 popup_size = Size2(400, 100); - Size2 window_size = p_gui_base->get_viewport_rect().size; - popup_size.x = MIN(window_size.x * 0.5, popup_size.x); - popup_size.y = MIN(window_size.y * 0.5, popup_size.y); + fetch_available_vcs_addon_names(); + List<StringName> available_addons = get_available_vcs_names(); + if (available_addons.size() >= 1) { - if (get_is_vcs_intialized()) { + Size2 popup_size = Size2(400, 100); + Size2 window_size = p_gui_base->get_viewport_rect().size; + popup_size.x = MIN(window_size.x * 0.5, popup_size.x); + popup_size.y = MIN(window_size.y * 0.5, popup_size.y); - set_up_init_button->set_disabled(true); - } + _populate_available_vcs_names(); - _populate_available_vcs_names(); + set_up_dialog->popup_centered_clamped(popup_size * EDSCALE); + } else { - set_up_dialog->popup_centered_clamped(popup_size * EDSCALE); + EditorNode::get_singleton()->show_warning(TTR("No VCS addons are available."), TTR("Error")); + } } void VersionControlEditorPlugin::_initialize_vcs() { @@ -120,7 +112,7 @@ void VersionControlEditorPlugin::_initialize_vcs() { return; } - int id = set_up_choice->get_selected_id(); + const int id = set_up_choice->get_selected_id(); String selected_addon = set_up_choice->get_item_text(id); String path = ScriptServer::get_global_class_path(selected_addon); @@ -381,7 +373,19 @@ void VersionControlEditorPlugin::register_editor() { void VersionControlEditorPlugin::fetch_available_vcs_addon_names() { - ScriptServer::get_global_class_list(&available_addons); + List<StringName> global_classes; + ScriptServer::get_global_class_list(&global_classes); + + for (int i = 0; i != global_classes.size(); i++) { + + String path = ScriptServer::get_global_class_path(global_classes[i]); + Ref<Script> script = ResourceLoader::load(path); + + if (script->get_instance_base_type() == "EditorVCSInterface") { + + available_addons.push_back(global_classes[i]); + } + } } void VersionControlEditorPlugin::clear_stage_area() { @@ -427,7 +431,6 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { version_control_actions->add_child(set_up_dialog); set_up_ok_button = set_up_dialog->get_ok(); - set_up_ok_button->set_disabled(false); set_up_ok_button->set_text(TTR("Close")); set_up_vbc = memnew(VBoxContainer); @@ -454,7 +457,6 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { set_up_init_settings = NULL; set_up_init_button = memnew(Button); - set_up_init_button->set_disabled(true); set_up_init_button->set_text(TTR("Initialize")); set_up_init_button->connect("pressed", this, "_initialize_vcs"); set_up_vbc->add_child(set_up_init_button); @@ -564,7 +566,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { diff_heading = memnew(Label); diff_heading->set_text(TTR("Status")); - diff_heading->set_tooltip(TTR("View file diffs before commiting them to the latest version")); + diff_heading->set_tooltip(TTR("View file diffs before committing them to the latest version")); diff_hbc->add_child(diff_heading); diff_file_name = memnew(Label); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 6b338ca02b..82baa99da2 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -2367,6 +2367,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("LessThanEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than or Equal (<=)")), VisualShaderNodeCompare::FUNC_LESS_THAN_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); add_options.push_back(AddOption("NotEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Not Equal (!=)")), VisualShaderNodeCompare::FUNC_NOT_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); add_options.push_back(AddOption("Switch", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated vector if the provided boolean value is true or false."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("SwitchS", "Conditional", "Functions", "VisualShaderNodeScalarSwitch", TTR("Returns an associated scalar if the provided boolean value is true or false."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); add_options.push_back(AddOption("Compare", "Conditional", "Common", "VisualShaderNodeCompare", TTR("Returns the boolean result of the comparison between two parameters."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); add_options.push_back(AddOption("Is", "Conditional", "Common", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF (or NaN) and a scalar parameter."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); @@ -2382,6 +2383,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("InvCamera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_camera"), "inv_camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("InvProjection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection"), "inv_projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Normal", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "normal"), "normal", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("OutputIsSRGB", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "output_is_srgb"), "output_is_srgb", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Projection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "camera"), "projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("ViewportSize", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "viewport_size"), "viewport_size", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); @@ -2412,6 +2414,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("Binormal", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal"), "binormal", VisualShaderNode::PORT_TYPE_VECTOR, VisualShader::TYPE_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Color", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, VisualShader::TYPE_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("FragCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, VisualShader::TYPE_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("FrontFacing", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "front_facing"), "front_facing", VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShader::TYPE_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("PointCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, VisualShader::TYPE_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, VisualShader::TYPE_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Side", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "side"), "side", VisualShaderNode::PORT_TYPE_SCALAR, VisualShader::TYPE_FRAGMENT, Shader::MODE_SPATIAL)); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index c6e3dc1e32..f70dcab931 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -928,16 +928,36 @@ public: TextureButton *favorite_button; TextureRect *icon; bool icon_needs_reload; + bool hover; ProjectListItemControl() { favorite_button = NULL; icon = NULL; icon_needs_reload = true; + hover = false; } void set_is_favorite(bool fav) { favorite_button->set_modulate(fav ? Color(1, 1, 1, 1) : Color(1, 1, 1, 0.2)); } + + void _notification(int p_what) { + switch (p_what) { + case NOTIFICATION_MOUSE_ENTER: { + hover = true; + update(); + } break; + case NOTIFICATION_MOUSE_EXIT: { + hover = false; + update(); + } break; + case NOTIFICATION_DRAW: { + if (hover) { + draw_style_box(get_stylebox("hover", "Tree"), Rect2(Point2(), get_size() - Size2(10, 0) * EDSCALE)); + } + } break; + } + } }; class ProjectList : public ScrollContainer { @@ -1260,6 +1280,8 @@ void ProjectList::create_project_item_control(int p_index) { TextureButton *favorite = memnew(TextureButton); favorite->set_name("FavoriteButton"); favorite->set_normal_texture(favorite_icon); + // This makes the project's "hover" style display correctly when hovering the favorite icon + favorite->set_mouse_filter(MOUSE_FILTER_PASS); favorite->connect("pressed", this, "_favorite_pressed", varray(hb)); favorite_box->add_child(favorite); favorite_box->set_alignment(BoxContainer::ALIGN_CENTER); @@ -1667,7 +1689,7 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) { emit_signal(SIGNAL_SELECTION_CHANGED); - if (mb->is_doubleclick()) { + if (!mb->get_control() && mb->is_doubleclick()) { emit_signal(SIGNAL_PROJECT_ASK_OPEN); } } @@ -1735,6 +1757,12 @@ void ProjectManager::_notification(int p_what) { if (_project_list->get_project_count() == 0 && StreamPeerSSL::is_available()) open_templates->popup_centered_minsize(); + + if (_project_list->get_project_count() >= 1) { + // Focus on the search box immediately to allow the user + // to search without having to reach for their mouse + project_filter->search_box->grab_focus(); + } } break; case NOTIFICATION_VISIBILITY_CHANGED: { diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index a0d2332ffc..d42f15cce8 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -1007,8 +1007,12 @@ void ProjectSettingsEditor::_copy_to_platform_about_to_show() { presets.insert("pvrtc"); presets.insert("debug"); presets.insert("release"); + presets.insert("editor"); + presets.insert("standalone"); presets.insert("32"); presets.insert("64"); + // Not available as an export platform yet, so it needs to be added manually + presets.insert("Server"); for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { List<String> p; @@ -1043,6 +1047,84 @@ void ProjectSettingsEditor::_copy_to_platform_about_to_show() { } } +Variant ProjectSettingsEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + + TreeItem *selected = input_editor->get_selected(); + if (!selected || selected->get_parent() != input_editor->get_root()) + return Variant(); + + String name = selected->get_text(0); + VBoxContainer *vb = memnew(VBoxContainer); + HBoxContainer *hb = memnew(HBoxContainer); + Label *label = memnew(Label(name)); + hb->set_modulate(Color(1, 1, 1, 1.0f)); + hb->add_child(label); + vb->add_child(hb); + set_drag_preview(vb); + + Dictionary drag_data; + drag_data["type"] = "nodes"; + + input_editor->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN); + + return drag_data; +} + +bool ProjectSettingsEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + + Dictionary d = p_data; + if (!d.has("type") || d["type"] != "nodes") + return false; + + TreeItem *selected = input_editor->get_selected(); + TreeItem *item = input_editor->get_item_at_position(p_point); + if (!selected || !item || item->get_parent() == selected) + return false; + + return true; +} + +void ProjectSettingsEditor::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 = input_editor->get_selected(); + TreeItem *item = input_editor->get_item_at_position(p_point); + TreeItem *target = item->get_parent() == input_editor->get_root() ? item : item->get_parent(); + + String selected_name = "input/" + selected->get_text(0); + int old_order = ProjectSettings::get_singleton()->get_order(selected_name); + String target_name = "input/" + target->get_text(0); + int target_order = ProjectSettings::get_singleton()->get_order(target_name); + + int order = old_order; + bool is_below = target_order > old_order; + TreeItem *iterator = is_below ? selected->get_next() : selected->get_prev(); + + undo_redo->create_action(TTR("Moved Input Action Event")); + while (iterator != target) { + + String iterator_name = "input/" + iterator->get_text(0); + int iterator_order = ProjectSettings::get_singleton()->get_order(iterator_name); + undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", iterator_name, order); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", iterator_name, iterator_order); + order = iterator_order; + iterator = is_below ? iterator->get_next() : iterator->get_prev(); + } + + undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", target_name, order); + undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", selected_name, target_order); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", target_name, target_order); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", selected_name, old_order); + + undo_redo->add_do_method(this, "_update_actions"); + undo_redo->add_undo_method(this, "_update_actions"); + undo_redo->add_do_method(this, "_settings_changed"); + undo_redo->add_undo_method(this, "_settings_changed"); + undo_redo->commit_action(); +} + void ProjectSettingsEditor::_copy_to_platform(int p_which) { String path = globals_editor->get_inspector()->get_selected_path(); @@ -1227,7 +1309,7 @@ void ProjectSettingsEditor::_translation_res_option_changed() { ERR_FAIL_COND(!remaps.has(key)); PoolStringArray r = remaps[key]; ERR_FAIL_INDEX(idx, r.size()); - if (translation_locales_idxs_remap.size() > 0) { + if (translation_locales_idxs_remap.size() > which) { r.set(idx, path + ":" + langs[translation_locales_idxs_remap[which]]); } else { r.set(idx, path + ":" + langs[which]); @@ -1310,7 +1392,7 @@ void ProjectSettingsEditor::_translation_res_option_delete(Object *p_item, int p void ProjectSettingsEditor::_translation_filter_option_changed() { int sel_id = translation_locale_filter_mode->get_selected_id(); - TreeItem *t = translation_filter->get_selected(); + TreeItem *t = translation_filter->get_edited(); String locale = t->get_tooltip(0); bool checked = t->is_checked(0); @@ -1662,6 +1744,10 @@ void ProjectSettingsEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_editor_restart_close"), &ProjectSettingsEditor::_editor_restart_close); ClassDB::bind_method(D_METHOD("get_tabs"), &ProjectSettingsEditor::get_tabs); + + ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &ProjectSettingsEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &ProjectSettingsEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &ProjectSettingsEditor::drop_data_fw); } ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { @@ -1844,6 +1930,8 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { input_editor->connect("item_activated", this, "_action_activated"); input_editor->connect("cell_selected", this, "_action_selected"); input_editor->connect("button_pressed", this, "_action_button_pressed"); + input_editor->set_drag_forwarding(this); + popup_add = memnew(PopupMenu); add_child(popup_add); popup_add->connect("id_pressed", this, "_add_item"); diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index d302c0d34b..4dfd8ba602 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -158,6 +158,10 @@ class ProjectSettingsEditor : public AcceptDialog { void _toggle_search_bar(bool p_pressed); + 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 _copy_to_platform_about_to_show(); ProjectSettingsEditor(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 16f1575757..2ddf1f7056 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -759,8 +759,13 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { _delete_confirm(); } else { - if (remove_list.size() > 1) { + if (remove_list.size() >= 2) { delete_dialog->set_text(vformat(TTR("Delete %d nodes?"), remove_list.size())); + } else if (remove_list.size() == 1 && remove_list[0] == editor_data->get_edited_scene_root()) { + delete_dialog->set_text(vformat(TTR("Delete the root node \"%s\"?"), remove_list[0]->get_name())); + } else if (remove_list.size() == 1 && remove_list[0]->get_filename() == "" && remove_list[0]->get_child_count() >= 1) { + // Display this message only for non-instanced scenes + delete_dialog->set_text(vformat(TTR("Delete node \"%s\" and its children?"), remove_list[0]->get_name())); } else { delete_dialog->set_text(vformat(TTR("Delete node \"%s\"?"), remove_list[0]->get_name())); } @@ -1504,7 +1509,7 @@ bool SceneTreeDock::_validate_no_foreign() { // When edited_scene inherits from another one the root Node will be the parent Scene, // we don't want to consider that Node a foreign one otherwise we would not be able to - // delete it + // delete it. if (edited_scene->get_scene_inherited_state().is_valid() && edited_scene == E->get()) { continue; } @@ -1528,7 +1533,7 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) { List<Node *> selection = editor_selection->get_selected_node_list(); if (selection.empty()) - return; //nothing to reparent + return; // Nothing to reparent. Vector<Node *> nodes; @@ -1544,18 +1549,32 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V Node *new_parent = p_new_parent; ERR_FAIL_COND(!new_parent); + if (p_nodes.size() == 0) + return; // Nothing to reparent. + + p_nodes.sort_custom<Node::Comparator>(); //Makes result reliable. + + bool no_change = true; + for (int ni = 0; ni < p_nodes.size(); ni++) { + + if (p_nodes[ni] == p_new_parent) + return; // Attempt to reparent to itself. + + if (p_nodes[ni]->get_parent() != p_new_parent || p_position_in_parent + ni != p_nodes[ni]->get_position_in_parent()) + no_change = false; + } + + if (no_change) + return; // Position and parent didn't change. + Node *validate = new_parent; while (validate) { - ERR_FAIL_COND_MSG(p_nodes.find(validate) != -1, "Selection changed at some point.. can't reparent."); + ERR_FAIL_COND_MSG(p_nodes.find(validate) != -1, "Selection changed at some point. Can't reparent."); validate = validate->get_parent(); } - //ok all valid - - if (p_nodes.size() == 0) - return; //nothing to reparent - //sort by tree order, so re-adding is easy + // Sort by tree order, so re-adding is easy. p_nodes.sort_custom<Node::Comparator>(); editor_data->get_undo_redo().create_action(TTR("Reparent Node")); @@ -1567,7 +1586,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V for (int ni = 0; ni < p_nodes.size(); ni++) { - //no undo for now, sorry + // No undo implemented for this yet. Node *node = p_nodes[ni]; fill_path_renames(node, new_parent, &path_renames); @@ -1577,14 +1596,11 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V node->get_owned_by(node->get_owner(), &owned); Array owners; for (List<Node *>::Element *E = owned.front(); E; E = E->next()) { - owners.push_back(E->get()); } - if (new_parent == node->get_parent() && node->get_index() < p_position_in_parent + ni) { - //if child will generate a gap when moved, adjust - inc--; - } + if (new_parent == node->get_parent() && node->get_index() < p_position_in_parent + ni) + inc--; // If the child will generate a gap when moved, adjust. editor_data->get_undo_redo().add_do_method(node->get_parent(), "remove_child", node); editor_data->get_undo_redo().add_do_method(new_parent, "add_child", node); @@ -1596,17 +1612,17 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V String old_name = former_names[ni]; String new_name = new_parent->validate_child_name(node); - // name was modified, fix the path renames + // Name was modified, fix the path renames. if (old_name.casecmp_to(new_name) != 0) { - // Fix the to name to have the new name + // Fix the to name to have the new name. NodePath old_new_name = path_renames[ni].second; NodePath new_path; Vector<StringName> unfixed_new_names = old_new_name.get_names(); Vector<StringName> fixed_new_names; - // Get last name and replace with fixed new name + // Get last name and replace with fixed new name. for (int a = 0; a < (unfixed_new_names.size() - 1); a++) { fixed_new_names.push_back(unfixed_new_names[a]); } @@ -1640,8 +1656,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V inc++; } - //add and move in a second step.. (so old order is preserved) - + // Add and move in a second step (so old order is preserved). for (int ni = 0; ni < p_nodes.size(); ni++) { Node *node = p_nodes[ni]; diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index a4270786c1..6ba507fb9c 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -33,6 +33,7 @@ #include "core/io/marshalls.h" #include "core/project_settings.h" #include "core/ustring.h" +#include "editor_network_profiler.h" #include "editor_node.h" #include "editor_profiler.h" #include "editor_settings.h" @@ -433,6 +434,15 @@ int ScriptEditorDebugger::_update_scene_tree(TreeItem *parent, const Array &node } item->set_metadata(0, id); + if (id == inspected_object_id) { + TreeItem *cti = item->get_parent(); + while (cti) { + cti->set_collapsed(false); + cti = cti->get_parent(); + } + item->select(0); + } + // Set current item as collapsed if necessary if (parent) { if (!unfold_cache.has(id)) { @@ -992,7 +1002,20 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da profiler->add_frame_metric(metric, false); else profiler->add_frame_metric(metric, true); - + } else if (p_msg == "network_profile") { + int frame_size = 6; + for (int i = 0; i < p_data.size(); i += frame_size) { + MultiplayerAPI::ProfilingInfo pi; + pi.node = p_data[i + 0]; + pi.node_path = p_data[i + 1]; + pi.incoming_rpc = p_data[i + 2]; + pi.incoming_rset = p_data[i + 3]; + pi.outgoing_rpc = p_data[i + 4]; + pi.outgoing_rset = p_data[i + 5]; + network_profiler->add_node_frame_data(pi); + } + } else if (p_msg == "network_bandwidth") { + network_profiler->set_bandwidth(p_data[0], p_data[1]); } else if (p_msg == "kill_me") { editor->call_deferred("stop_child_process"); @@ -1028,17 +1051,15 @@ void ScriptEditorDebugger::_performance_draw() { which.push_back(i); } - Ref<Font> graph_font = get_font("font", "TextEdit"); - if (which.empty()) { - String text = TTR("Pick one or more items from the list to display the graph."); - - perf_draw->draw_string(graph_font, Point2i(MAX(0, perf_draw->get_size().x - graph_font->get_string_size(text).x), perf_draw->get_size().y + graph_font->get_ascent()) / 2, text, get_color("font_color", "Label"), perf_draw->get_size().x); - + info_message->show(); return; } + info_message->hide(); + Ref<StyleBox> graph_sb = get_stylebox("normal", "TextEdit"); + Ref<Font> graph_font = get_font("font", "TextEdit"); int cols = Math::ceil(Math::sqrt((float)which.size())); int rows = Math::ceil((float)which.size() / cols); @@ -1107,7 +1128,6 @@ void ScriptEditorDebugger::_notification(int p_what) { forward->set_icon(get_icon("Forward", "EditorIcons")); dobreak->set_icon(get_icon("Pause", "EditorIcons")); docontinue->set_icon(get_icon("DebugContinue", "EditorIcons")); - //scene_tree_refresh->set_icon( get_icon("Reload","EditorIcons")); le_set->connect("pressed", this, "_live_edit_set"); le_clear->connect("pressed", this, "_live_edit_clear"); error_tree->connect("item_selected", this, "_error_selected"); @@ -1179,7 +1199,7 @@ void ScriptEditorDebugger::_notification(int p_what) { if (connection.is_null()) break; - EditorNode::get_log()->add_message("** Debug Process Started **"); + EditorNode::get_log()->add_message("--- Debugging process started ---", EditorLog::MSG_TYPE_EDITOR); ppeer->set_stream_peer(connection); @@ -1189,7 +1209,7 @@ void ScriptEditorDebugger::_notification(int p_what) { dobreak->set_disabled(false); tabs->set_current_tab(0); - _set_reason_text(TTR("Child Process Connected"), MESSAGE_SUCCESS); + _set_reason_text(TTR("Child process connected."), MESSAGE_SUCCESS); profiler->clear(); inspect_scene_tree->clear(); @@ -1208,6 +1228,10 @@ void ScriptEditorDebugger::_notification(int p_what) { if (profiler->is_profiling()) { _profiler_activate(true); } + + if (network_profiler->is_profiling()) { + _network_profiler_activate(true); + } } } @@ -1373,7 +1397,7 @@ void ScriptEditorDebugger::stop() { ppeer->set_stream_peer(Ref<StreamPeer>()); if (connection.is_valid()) { - EditorNode::get_log()->add_message("** Debug Process Stopped **"); + EditorNode::get_log()->add_message("--- Debugging process stopped ---", EditorLog::MSG_TYPE_EDITOR); connection.unref(); reason->set_text(""); @@ -1391,7 +1415,7 @@ void ScriptEditorDebugger::stop() { profiler->set_enabled(true); inspect_scene_tree->clear(); - EditorNode::get_singleton()->edit_current(); + inspector->edit(NULL); EditorNode::get_singleton()->get_pause_button()->set_pressed(false); EditorNode::get_singleton()->get_pause_button()->set_disabled(true); EditorNode::get_singleton()->get_scene_tree_dock()->hide_remote_tree(); @@ -1427,6 +1451,25 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable) { } } +void ScriptEditorDebugger::_network_profiler_activate(bool p_enable) { + + if (!connection.is_valid()) + return; + + if (p_enable) { + Array msg; + msg.push_back("start_network_profiling"); + ppeer->put_var(msg); + print_verbose("Starting network profiling."); + + } else { + Array msg; + msg.push_back("stop_network_profiling"); + ppeer->put_var(msg); + print_verbose("Ending network profiling."); + } +} + void ScriptEditorDebugger::_profiler_seeked() { if (!connection.is_valid() || !connection->is_connected_to_host()) @@ -2020,6 +2063,7 @@ void ScriptEditorDebugger::_bind_methods() { ClassDB::bind_method(D_METHOD("_expand_errors_list"), &ScriptEditorDebugger::_expand_errors_list); ClassDB::bind_method(D_METHOD("_collapse_errors_list"), &ScriptEditorDebugger::_collapse_errors_list); ClassDB::bind_method(D_METHOD("_profiler_activate"), &ScriptEditorDebugger::_profiler_activate); + ClassDB::bind_method(D_METHOD("_network_profiler_activate"), &ScriptEditorDebugger::_network_profiler_activate); ClassDB::bind_method(D_METHOD("_profiler_seeked"), &ScriptEditorDebugger::_profiler_seeked); ClassDB::bind_method(D_METHOD("_clear_errors_list"), &ScriptEditorDebugger::_clear_errors_list); @@ -2104,11 +2148,13 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { step = memnew(ToolButton); hbc->add_child(step); step->set_tooltip(TTR("Step Into")); + step->set_shortcut(ED_GET_SHORTCUT("debugger/step_into")); step->connect("pressed", this, "debug_step"); next = memnew(ToolButton); hbc->add_child(next); next->set_tooltip(TTR("Step Over")); + next->set_shortcut(ED_GET_SHORTCUT("debugger/step_over")); next->connect("pressed", this, "debug_next"); hbc->add_child(memnew(VSeparator)); @@ -2116,11 +2162,13 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { dobreak = memnew(ToolButton); hbc->add_child(dobreak); dobreak->set_tooltip(TTR("Break")); + dobreak->set_shortcut(ED_GET_SHORTCUT("debugger/break")); dobreak->connect("pressed", this, "debug_break"); docontinue = memnew(ToolButton); hbc->add_child(docontinue); docontinue->set_tooltip(TTR("Continue")); + docontinue->set_shortcut(ED_GET_SHORTCUT("debugger/continue")); docontinue->connect("pressed", this, "debug_continue"); back = memnew(Button); @@ -2245,6 +2293,13 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { profiler->connect("break_request", this, "_profiler_seeked"); } + { //network profiler + network_profiler = memnew(EditorNetworkProfiler); + network_profiler->set_name(TTR("Network Profiler")); + tabs->add_child(network_profiler); + network_profiler->connect("enable_profiling", this, "_network_profiler_activate"); + } + { //monitors HSplitContainer *hsp = memnew(HSplitContainer); @@ -2254,11 +2309,14 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { perf_monitors->set_column_title(0, TTR("Monitor")); perf_monitors->set_column_title(1, TTR("Value")); perf_monitors->set_column_titles_visible(true); - hsp->add_child(perf_monitors); perf_monitors->connect("item_edited", this, "_performance_select"); + hsp->add_child(perf_monitors); + perf_draw = memnew(Control); + perf_draw->set_clip_contents(true); perf_draw->connect("draw", this, "_performance_draw"); hsp->add_child(perf_draw); + hsp->set_name(TTR("Monitors")); hsp->set_split_offset(340 * EDSCALE); tabs->add_child(hsp); @@ -2292,6 +2350,14 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { perf_items.push_back(it); perf_max.write[i] = 0; } + + info_message = memnew(Label); + info_message->set_text(TTR("Pick one or more items from the list to display the graph.")); + info_message->set_valign(Label::VALIGN_CENTER); + info_message->set_align(Label::ALIGN_CENTER); + info_message->set_autowrap(true); + info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); + perf_draw->add_child(info_message); } { //vmem inspect @@ -2303,7 +2369,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { vmem_hb->add_child(memnew(Label(TTR("Total:") + " "))); vmem_total = memnew(LineEdit); vmem_total->set_editable(false); - vmem_total->set_custom_minimum_size(Size2(100, 1) * EDSCALE); + vmem_total->set_custom_minimum_size(Size2(100, 0) * EDSCALE); vmem_hb->add_child(vmem_total); vmem_refresh = memnew(ToolButton); vmem_hb->add_child(vmem_refresh); diff --git a/editor/script_editor_debugger.h b/editor/script_editor_debugger.h index dc9c99591d..cc284476c0 100644 --- a/editor/script_editor_debugger.h +++ b/editor/script_editor_debugger.h @@ -50,6 +50,7 @@ class TreeItem; class HSplitContainer; class ItemList; class EditorProfiler; +class EditorNetworkProfiler; class ScriptEditorDebuggerInspectedObject; @@ -134,6 +135,7 @@ class ScriptEditorDebugger : public Control { Tree *perf_monitors; Control *perf_draw; + Label *info_message; Tree *vmem_tree; Button *vmem_refresh; @@ -155,6 +157,7 @@ class ScriptEditorDebugger : public Control { Map<String, int> res_path_cache; EditorProfiler *profiler; + EditorNetworkProfiler *network_profiler; EditorNode *editor; @@ -199,6 +202,8 @@ class ScriptEditorDebugger : public Control { void _profiler_activate(bool p_enable); void _profiler_seeked(); + void _network_profiler_activate(bool p_enable); + void _paused(); void _set_remote_object(ObjectID p_id, ScriptEditorDebuggerInspectedObject *p_obj); diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index b4643231d7..c9c1f9c3e0 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -110,7 +110,7 @@ void EditorSettingsDialog::_filter_shortcuts(const String &p_filter) { } void EditorSettingsDialog::_undo_redo_callback(void *p_self, const String &p_name) { - EditorNode::get_log()->add_message(p_name); + EditorNode::get_log()->add_message(p_name, EditorLog::MSG_TYPE_EDITOR); } void EditorSettingsDialog::_notification(int p_what) { @@ -151,7 +151,7 @@ void EditorSettingsDialog::_unhandled_input(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("editor/undo", p_event)) { String action = undo_redo->get_current_action_name(); if (action != "") - EditorNode::get_log()->add_message("UNDO: " + action); + EditorNode::get_log()->add_message("Undo: " + action, EditorLog::MSG_TYPE_EDITOR); undo_redo->undo(); handled = true; } @@ -159,7 +159,7 @@ void EditorSettingsDialog::_unhandled_input(const Ref<InputEvent> &p_event) { undo_redo->redo(); String action = undo_redo->get_current_action_name(); if (action != "") - EditorNode::get_log()->add_message("REDO: " + action); + EditorNode::get_log()->add_message("Redo: " + action, EditorLog::MSG_TYPE_EDITOR); handled = true; } diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index 4c5371769f..9f940c46e6 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -172,8 +172,8 @@ void EditorSpatialGizmo::Instance::create_instance(Spatial *p_base, bool p_hidde instance = VS::get_singleton()->instance_create2(mesh->get_rid(), p_base->get_world()->get_scenario()); VS::get_singleton()->instance_attach_object_instance_id(instance, p_base->get_instance_id()); - if (skeleton.is_valid()) - VS::get_singleton()->instance_attach_skeleton(instance, skeleton); + if (skin_reference.is_valid()) + VS::get_singleton()->instance_attach_skeleton(instance, skin_reference->get_skeleton()); if (extra_margin) VS::get_singleton()->instance_set_extra_visibility_margin(instance, 1); VS::get_singleton()->instance_geometry_set_cast_shadows_setting(instance, VS::SHADOW_CASTING_SETTING_OFF); @@ -181,14 +181,14 @@ void EditorSpatialGizmo::Instance::create_instance(Spatial *p_base, bool p_hidde VS::get_singleton()->instance_set_layer_mask(instance, layer); //gizmos are 26 } -void EditorSpatialGizmo::add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard, const RID &p_skeleton, const Ref<Material> &p_material) { +void EditorSpatialGizmo::add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard, const Ref<SkinReference> &p_skin_reference, const Ref<Material> &p_material) { ERR_FAIL_COND(!spatial_node); Instance ins; ins.billboard = p_billboard; ins.mesh = p_mesh; - ins.skeleton = p_skeleton; + ins.skin_reference = p_skin_reference; ins.material = p_material; if (valid) { ins.create_instance(spatial_node, hidden); @@ -783,9 +783,10 @@ Vector3 EditorSpatialGizmo::get_handle_pos(int p_idx) const { LightSpatialGizmoPlugin::LightSpatialGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/light", Color(1, 1, 0.2)); + Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/light", Color(1, 1, 0.7)); - create_material("lines", gizmo_color); + create_material("lines_primary", gizmo_color); + create_material("lines_secondary", gizmo_color * Color(1, 1, 1, 0.35)); create_material("lines_billboard", gizmo_color, true); create_icon_material("light_directional_icon", SpatialEditor::get_singleton()->get_icon("GizmoDirectionalLight", "EditorIcons")); @@ -937,7 +938,7 @@ void LightSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { if (Object::cast_to<DirectionalLight>(light)) { - Ref<Material> material = get_material("lines", p_gizmo); + Ref<Material> material = get_material("lines_primary", p_gizmo); Ref<Material> icon = get_material("light_directional_icon", p_gizmo); const int arrow_points = 7; @@ -975,31 +976,39 @@ void LightSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { if (Object::cast_to<OmniLight>(light)) { - Ref<Material> material = get_material("lines_billboard", p_gizmo); - Ref<Material> icon = get_material("light_omni_icon", p_gizmo); + // Use both a billboard circle and 3 non-billboard circles for a better sphere-like representation + const Ref<Material> lines_material = get_material("lines_secondary", p_gizmo); + const Ref<Material> lines_billboard_material = get_material("lines_billboard", p_gizmo); + const Ref<Material> icon = get_material("light_omni_icon", p_gizmo); OmniLight *on = Object::cast_to<OmniLight>(light); - - float r = on->get_param(Light::PARAM_RANGE); - + const float r = on->get_param(Light::PARAM_RANGE); Vector<Vector3> points; + Vector<Vector3> points_billboard; - for (int i = 0; i <= 360; i++) { + for (int i = 0; i < 120; i++) { - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + 1); - Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; - Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; + // Create a circle + const float ra = Math::deg2rad((float)(i * 3)); + const float rb = Math::deg2rad((float)((i + 1) * 3)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; - /*points.push_back(Vector3(a.x,0,a.y)); - points.push_back(Vector3(b.x,0,b.y)); - points.push_back(Vector3(0,a.x,a.y)); - points.push_back(Vector3(0,b.x,b.y));*/ + // Draw axis-aligned circles + points.push_back(Vector3(a.x, 0, a.y)); + points.push_back(Vector3(b.x, 0, b.y)); + points.push_back(Vector3(0, a.x, a.y)); + points.push_back(Vector3(0, b.x, b.y)); points.push_back(Vector3(a.x, a.y, 0)); points.push_back(Vector3(b.x, b.y, 0)); + + // Draw a billboarded circle + points_billboard.push_back(Vector3(a.x, a.y, 0)); + points_billboard.push_back(Vector3(b.x, b.y, 0)); } - p_gizmo->add_lines(points, material, true); + p_gizmo->add_lines(points, lines_material, true); + p_gizmo->add_lines(points_billboard, lines_billboard_material, true); p_gizmo->add_unscaled_billboard(icon, 0.05); Vector<Vector3> handles; @@ -1009,40 +1018,44 @@ void LightSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { if (Object::cast_to<SpotLight>(light)) { - Ref<Material> material = get_material("lines", p_gizmo); - Ref<Material> icon = get_material("light_spot_icon", p_gizmo); + const Ref<Material> material_primary = get_material("lines_primary", p_gizmo); + const Ref<Material> material_secondary = get_material("lines_secondary", p_gizmo); + const Ref<Material> icon = get_material("light_spot_icon", p_gizmo); - Vector<Vector3> points; + Vector<Vector3> points_primary; + Vector<Vector3> points_secondary; SpotLight *sl = Object::cast_to<SpotLight>(light); float r = sl->get_param(Light::PARAM_RANGE); float w = r * Math::sin(Math::deg2rad(sl->get_param(Light::PARAM_SPOT_ANGLE))); float d = r * Math::cos(Math::deg2rad(sl->get_param(Light::PARAM_SPOT_ANGLE))); - for (int i = 0; i < 360; i++) { - - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + 1); - Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w; - Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w; + for (int i = 0; i < 120; i++) { - points.push_back(Vector3(a.x, a.y, -d)); - points.push_back(Vector3(b.x, b.y, -d)); + // Draw a circle + const float ra = Math::deg2rad((float)(i * 3)); + const float rb = Math::deg2rad((float)((i + 1) * 3)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w; - if (i % 90 == 0) { + points_primary.push_back(Vector3(a.x, a.y, -d)); + points_primary.push_back(Vector3(b.x, b.y, -d)); - points.push_back(Vector3(a.x, a.y, -d)); - points.push_back(Vector3()); + if (i % 15 == 0) { + // Draw 8 lines from the cone origin to the sides of the circle + points_secondary.push_back(Vector3(a.x, a.y, -d)); + points_secondary.push_back(Vector3()); } } - points.push_back(Vector3(0, 0, -r)); - points.push_back(Vector3()); + points_primary.push_back(Vector3(0, 0, -r)); + points_primary.push_back(Vector3()); - p_gizmo->add_lines(points, material); + p_gizmo->add_lines(points_primary, material_primary); + p_gizmo->add_lines(points_secondary, material_secondary); - float ra = 16 * Math_PI * 2.0 / 64.0; - Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w; + const float ra = 16 * Math_PI * 2.0 / 64.0; + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w; Vector<Vector3> handles; handles.push_back(Vector3(0, 0, -r)); @@ -1789,7 +1802,7 @@ void SkeletonSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { } Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, false, skel->get_skeleton()); + p_gizmo->add_mesh(m, false, skel->register_skin(Ref<Skin>())); } //// @@ -3712,7 +3725,7 @@ void CollisionShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { Ref<ConcavePolygonShape> cs2 = s; Ref<ArrayMesh> mesh = cs2->get_debug_mesh(); - p_gizmo->add_mesh(mesh, false, RID(), material); + p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), material); } if (Object::cast_to<RayShape>(*s)) { @@ -3734,7 +3747,7 @@ void CollisionShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { Ref<HeightMapShape> hms = s; Ref<ArrayMesh> mesh = hms->get_debug_mesh(); - p_gizmo->add_mesh(mesh, false, RID(), material); + p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), material); } } |