diff options
Diffstat (limited to 'modules')
33 files changed, 691 insertions, 391 deletions
diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp index 2ccfd5d326..9c9302a51c 100644 --- a/modules/enet/enet_connection.cpp +++ b/modules/enet/enet_connection.cpp @@ -113,7 +113,7 @@ Ref<ENetPacketPeer> ENetConnection::connect_to_host(const String &p_address, int if (peer == nullptr) { return nullptr; } - out = Ref<ENetPacketPeer>(memnew(ENetPacketPeer(peer))); + out.instantiate(peer); peers.push_back(out); return out; } diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index ede4ce6617..0355119442 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -666,7 +666,19 @@ @export var car_label = "Speedy" @export var car_number = 3 [/codeblock] - [b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups. + [b]Note:[/b] Subgroups cannot be nested, but you can use the slash separator ([code]/[/code]) to achieve the desired effect: + [codeblock] + @export_group("Car Properties") + @export_subgroup("Wheels", "wheel_") + @export_subgroup("Wheels/Front", "front_wheel_") + @export var front_wheel_strength = 10 + @export var front_wheel_mobility = 5 + @export_subgroup("Wheels/Rear", "rear_wheel_") + @export var rear_wheel_strength = 8 + @export var rear_wheel_mobility = 3 + @export_subgroup("Wheels", "wheel_") + @export var wheel_material: PhysicsMaterial + [/codeblock] </description> </annotation> <annotation name="@export_tool_button"> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 5f7a7e2915..c3fa59dc23 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -16,11 +16,10 @@ <return type="Variant" /> <description> Returns a new instance of the script. - For example: [codeblock] var MyClass = load("myclass.gd") var instance = MyClass.new() - assert(instance.get_script() == MyClass) + print(instance.get_script() == MyClass) # Prints true [/codeblock] </description> </method> diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 376c3c89f5..c81c2f09f0 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -306,7 +306,7 @@ The binary buffer attached to a .glb file. </member> <member name="import_as_skeleton_bones" type="bool" setter="set_import_as_skeleton_bones" getter="get_import_as_skeleton_bones" default="false"> - True to force all GLTFNodes in the document to be bones of a single Skeleton3D godot node. + If [code]true[/code], forces all GLTFNodes in the document to be bones of a single [Skeleton3D] Godot node. </member> <member name="json" type="Dictionary" setter="set_json" getter="get_json" default="{}"> The original raw JSON document corresponding to this GLTFState. diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index aa482615da..7cac61304f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -117,7 +117,7 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { mat_name = mat->get_name(); } else { // Assign default material when no material is assigned. - mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + mat.instantiate(); } importer_mesh->add_surface(p_mesh->surface_get_primitive_type(surface_i), array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat, @@ -5913,7 +5913,7 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd mat_name = mat->get_name(); } else { // Assign default material when no material is assigned. - mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + mat.instantiate(); } mesh->add_surface(csg_mesh->surface_get_primitive_type(surface_i), @@ -8560,7 +8560,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint Error err; Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); - ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); + ERR_FAIL_COND_V_MSG(err != OK, err, vformat(R"(Can't open file at path "%s")", p_path)); ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN); String base_path = p_base_path; if (base_path.is_empty()) { diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h index 37c8f6925c..73ef02e9f1 100644 --- a/modules/gltf/tests/test_gltf_extras.h +++ b/modules/gltf/tests/test_gltf_extras.h @@ -91,7 +91,7 @@ static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) { options["gltf/naming_version"] = 1; // Process gltf file, note that this generates `.scn` resource from the 2nd argument. - err = import_scene->import(p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr); + err = import_scene->import(0, p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr); CHECK_MESSAGE(err == OK, "GLTF import failed."); ResourceImporterScene::remove_scene_importer(import_gltf); diff --git a/modules/godot_physics_3d/godot_shape_3d.cpp b/modules/godot_physics_3d/godot_shape_3d.cpp index a7cccc5cb8..4356ebe2f2 100644 --- a/modules/godot_physics_3d/godot_shape_3d.cpp +++ b/modules/godot_physics_3d/godot_shape_3d.cpp @@ -1996,7 +1996,11 @@ bool GodotHeightMapShape3D::intersect_segment(const Vector3 &p_begin, const Vect Vector3 bounds_from = p_begin / BOUNDS_CHUNK_SIZE; Vector3 bounds_to = p_end / BOUNDS_CHUNK_SIZE; Vector3 bounds_offset = local_origin / BOUNDS_CHUNK_SIZE; - return _intersect_grid_segment(_heightmap_chunk_cull_segment, bounds_from, bounds_to, bounds_grid_width, bounds_grid_depth, bounds_offset, r_point, r_normal); + // Plus 1 here to width and depth of the chunk because _intersect_grid_segment() is used by cell level as well, + // and in _intersect_grid_segment() the loop will exit 1 early because for cell point triangle lookup, it dose x + 1, z + 1 etc for the vertex. + int bounds_width = bounds_grid_width + 1; + int bounds_depth = bounds_grid_depth + 1; + return _intersect_grid_segment(_heightmap_chunk_cull_segment, bounds_from, bounds_to, bounds_width, bounds_depth, bounds_offset, r_point, r_normal); } } diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index bd2792d92a..caa7a79874 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -34,11 +34,14 @@ #include "core/input/input.h" #include "core/os/keyboard.h" +#include "editor/editor_command_palette.h" #include "editor/editor_main_screen.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_bottom_panel.h" +#include "editor/gui/editor_zoom_widget.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/3d/camera_3d.h" @@ -84,16 +87,10 @@ void GridMapEditor::_menu_option(int p_option) { } if (edit_axis != new_axis) { - int item1 = options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL); - int item2 = options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL); if (edit_axis == Vector3::AXIS_Y) { - options->get_popup()->set_item_text(item1, TTR("Next Plane")); - options->get_popup()->set_item_text(item2, TTR("Previous Plane")); - spin_box_label->set_text(TTR("Plane:")); + floor->set_tooltip_text("Change Grid Plane"); } else if (new_axis == Vector3::AXIS_Y) { - options->get_popup()->set_item_text(item1, TTR("Next Floor")); - options->get_popup()->set_item_text(item2, TTR("Previous Floor")); - spin_box_label->set_text(TTR("Floor:")); + floor->set_tooltip_text("Change Grid Floor"); } } edit_axis = Vector3::Axis(new_axis); @@ -251,14 +248,22 @@ void GridMapEditor::_menu_option(int p_option) { void GridMapEditor::_update_cursor_transform() { cursor_transform = Transform3D(); cursor_transform.origin = cursor_origin; - cursor_transform.basis = node->get_basis_with_orthogonal_index(cursor_rot); cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; - if (selected_palette >= 0) { - if (node && !node->get_mesh_library().is_null()) { + if (mode_buttons_group->get_pressed_button() == paint_mode_button) { + // Rotation is only applied in paint mode, we don't want the cursor box to rotate otherwise. + cursor_transform.basis = node->get_basis_with_orthogonal_index(cursor_rot); + if (selected_palette >= 0 && node && node->get_mesh_library().is_valid()) { cursor_transform *= node->get_mesh_library()->get_item_mesh_transform(selected_palette); } + } else { + Transform3D xf; + xf.scale(node->get_cell_size()); + xf.origin.x = node->get_center_x() ? -node->get_cell_size().x / 2 : 0; + xf.origin.y = node->get_center_y() ? -node->get_cell_size().y / 2 : 0; + xf.origin.z = node->get_center_z() ? -node->get_cell_size().z / 2 : 0; + cursor_transform *= xf; } if (cursor_instance.is_valid()) { @@ -301,7 +306,7 @@ void GridMapEditor::_update_selection_transform() { xf2.basis.scale(scale); xf2.origin = position; - RenderingServer::get_singleton()->instance_set_transform(selection_level_instance[i], xf2); + RenderingServer::get_singleton()->instance_set_transform(selection_level_instance[i], node->get_global_transform() * xf2); } } } @@ -336,25 +341,22 @@ void GridMapEditor::_set_selection(bool p_active, const Vector3 &p_begin, const if (is_visible_in_tree()) { _update_selection_transform(); } - - options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CLEAR), !selection.active); - options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CUT), !selection.active); - options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_DUPLICATE), !selection.active); - options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_FILL), !selection.active); } bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, bool p_click) { if (!spatial_editor) { return false; } - - if (selected_palette < 0 && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE) { + if (input_action == INPUT_TRANSFORM) { + return false; + } + if (selected_palette < 0 && input_action != INPUT_NONE && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE) { return false; } if (mesh_library.is_null()) { return false; } - if (input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE && !mesh_library->has_item(selected_palette)) { + if (input_action != INPUT_NONE && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE && !mesh_library->has_item(selected_palette)) { return false; } @@ -405,13 +407,17 @@ bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, b cursor_origin = (Vector3(cell[0], cell[1], cell[2]) + Vector3(0.5 * node->get_center_x(), 0.5 * node->get_center_y(), 0.5 * node->get_center_z())) * node->get_cell_size(); cursor_visible = true; - if (input_action == INPUT_SELECT || input_action == INPUT_PASTE) { + if (input_action == INPUT_PASTE) { cursor_visible = false; } _update_cursor_transform(); } + if (input_action == INPUT_NONE) { + return false; + } + if (input_action == INPUT_PASTE) { paste_indicator.current = Vector3i(cell[0], cell[1], cell[2]); _update_paste_indicator(); @@ -604,7 +610,18 @@ void GridMapEditor::_do_paste() { } if (reselect) { - undo_redo->add_do_method(this, "_set_selection", true, paste_indicator.begin + ofs, paste_indicator.end + ofs); + // We need to rotate the paste_indicator to find the selection begin and end: + Vector3 temp_end = rot.xform(paste_indicator.end - paste_indicator.begin) + paste_indicator.begin + ofs; + Vector3 temp_begin = paste_indicator.begin + ofs; + // _set_selection expects that selection_begin is the corner closer to the origin: + for (int i = 0; i < 3; ++i) { + if (temp_begin[i] > temp_end[i]) { + float p = temp_begin[i]; + temp_begin[i] = temp_end[i]; + temp_end[i] = p; + } + } + undo_redo->add_do_method(this, "_set_selection", true, temp_begin, temp_end); undo_redo->add_undo_method(this, "_set_selection", selection.active, selection.begin, selection.end); } @@ -613,13 +630,93 @@ void GridMapEditor::_do_paste() { _clear_clipboard_data(); } +void GridMapEditor::_show_viewports_transform_gizmo(bool p_value) { + Dictionary new_state; + new_state["transform_gizmo"] = p_value; + for (uint32_t i = 0; i < Node3DEditor::VIEWPORTS_COUNT; i++) { + Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(i); + viewport->set_state(new_state); + } +} + EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!node) { return EditorPlugin::AFTER_GUI_INPUT_PASS; } - Ref<InputEventMouseButton> mb = p_event; + Ref<InputEventKey> k = p_event; + if (k.is_valid() && k->is_pressed() && !k->is_echo()) { + // If we are in Transform mode we pass the events to the 3D editor, + // but if the Transform mode shortcut is pressed again, we go back to Selection mode. + if (mode_buttons_group->get_pressed_button() == transform_mode_button) { + if (transform_mode_button->get_shortcut().is_valid() && transform_mode_button->get_shortcut()->matches_event(p_event)) { + select_mode_button->set_pressed(true); + accept_event(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + return EditorPlugin::AFTER_GUI_INPUT_PASS; + } + + for (BaseButton *b : viewport_shortcut_buttons) { + if (b->is_disabled()) { + continue; + } + + if (b->get_shortcut().is_valid() && b->get_shortcut()->matches_event(p_event)) { + if (b->is_toggle_mode()) { + b->set_pressed(b->get_button_group().is_valid() || !b->is_pressed()); + } else { + // Can't press a button without toggle mode, so just emit the signal directly. + b->emit_signal(SceneStringName(pressed)); + } + accept_event(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + } + + if (k.is_valid() && k->is_pressed() && !k->is_echo()) { + if (k->get_keycode() == Key::ESCAPE) { + if (input_action == INPUT_PASTE) { + _clear_clipboard_data(); + input_action = INPUT_NONE; + _update_paste_indicator(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } else if (selection.active) { + _set_selection(false); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } else { + input_action = INPUT_NONE; + update_palette(); + _update_cursor_instance(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + + Ref<Shortcut> ed_shortcut = ED_GET_SHORTCUT("grid_map/previous_floor"); + if (ed_shortcut.is_valid() && ed_shortcut->matches_event(p_event)) { + accept_event(); + _menu_option(MENU_OPTION_PREV_LEVEL); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + ed_shortcut = ED_GET_SHORTCUT("grid_map/next_floor"); + if (ed_shortcut.is_valid() && ed_shortcut->matches_event(p_event)) { + accept_event(); + _menu_option(MENU_OPTION_NEXT_LEVEL); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + for (int i = 0; i < options->get_popup()->get_item_count(); ++i) { + const Ref<Shortcut> &shortcut = options->get_popup()->get_item_shortcut(i); + if (shortcut.is_valid() && shortcut->matches_event(p_event)) { + // Consume input to avoid conflicts with other plugins. + accept_event(); + _menu_option(options->get_popup()->get_item_id(i)); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + } + Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::WHEEL_UP && (mb->is_command_or_control_pressed())) { if (mb->is_pressed()) { @@ -645,14 +742,17 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D input_action = INPUT_NONE; _update_paste_indicator(); return EditorPlugin::AFTER_GUI_INPUT_STOP; - } else if (mb->is_shift_pressed() && can_edit) { + } else if (mode_buttons_group->get_pressed_button() == select_mode_button && can_edit) { input_action = INPUT_SELECT; last_selection = selection; - } else if (mb->is_command_or_control_pressed() && can_edit) { + } else if (mode_buttons_group->get_pressed_button() == pick_mode_button && can_edit) { input_action = INPUT_PICK; - } else { + } else if (mode_buttons_group->get_pressed_button() == paint_mode_button && can_edit) { input_action = INPUT_PAINT; set_items.clear(); + } else if (mode_buttons_group->get_pressed_button() == erase_mode_button && can_edit) { + input_action = INPUT_ERASE; + set_items.clear(); } } else if (mb->get_button_index() == MouseButton::RIGHT) { if (input_action == INPUT_PASTE) { @@ -663,9 +763,6 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D } else if (selection.active) { _set_selection(false); return EditorPlugin::AFTER_GUI_INPUT_STOP; - } else { - input_action = INPUT_ERASE; - set_items.clear(); } } else { return EditorPlugin::AFTER_GUI_INPUT_PASS; @@ -676,7 +773,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D } return EditorPlugin::AFTER_GUI_INPUT_PASS; } else { - if ((mb->get_button_index() == MouseButton::RIGHT && input_action == INPUT_ERASE) || (mb->get_button_index() == MouseButton::LEFT && input_action == INPUT_PAINT)) { + if ((mb->get_button_index() == MouseButton::LEFT && input_action == INPUT_ERASE) || (mb->get_button_index() == MouseButton::LEFT && input_action == INPUT_PAINT)) { if (set_items.size()) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("GridMap Paint")); @@ -731,42 +828,6 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D return EditorPlugin::AFTER_GUI_INPUT_PASS; } - Ref<InputEventKey> k = p_event; - - if (k.is_valid()) { - if (k->is_pressed()) { - if (k->get_keycode() == Key::ESCAPE) { - if (input_action == INPUT_PASTE) { - _clear_clipboard_data(); - input_action = INPUT_NONE; - _update_paste_indicator(); - return EditorPlugin::AFTER_GUI_INPUT_STOP; - } else if (selection.active) { - _set_selection(false); - return EditorPlugin::AFTER_GUI_INPUT_STOP; - } else { - selected_palette = -1; - mesh_library_palette->deselect_all(); - update_palette(); - _update_cursor_instance(); - return EditorPlugin::AFTER_GUI_INPUT_STOP; - } - } - - // Consume input to avoid conflicts with other plugins. - if (k.is_valid() && k->is_pressed() && !k->is_echo()) { - for (int i = 0; i < options->get_popup()->get_item_count(); ++i) { - const Ref<Shortcut> &shortcut = options->get_popup()->get_item_shortcut(i); - if (shortcut.is_valid() && shortcut->matches_event(p_event)) { - accept_event(); - _menu_option(options->get_popup()->get_item_id(i)); - return EditorPlugin::AFTER_GUI_INPUT_STOP; - } - } - } - } - } - Ref<InputEventPanGesture> pan_gesture = p_event; if (pan_gesture.is_valid()) { if (pan_gesture->is_alt_pressed() && pan_gesture->is_command_or_control_pressed()) { @@ -833,11 +894,13 @@ void GridMapEditor::_mesh_library_palette_input(const Ref<InputEvent> &p_ie) { // Zoom in/out using Ctrl + mouse wheel if (mb.is_valid() && mb->is_pressed() && mb->is_command_or_control_pressed()) { if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) { - size_slider->set_value(size_slider->get_value() + 0.2); + zoom_widget->set_zoom(zoom_widget->get_zoom() + 0.2); + zoom_widget->emit_signal(SNAME("zoom_changed"), zoom_widget->get_zoom()); } if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) { - size_slider->set_value(size_slider->get_value() - 0.2); + zoom_widget->set_zoom(zoom_widget->get_zoom() - 0.2); + zoom_widget->emit_signal(SNAME("zoom_changed"), zoom_widget->get_zoom()); } } } @@ -855,9 +918,9 @@ void GridMapEditor::update_palette() { if (display_mode == DISPLAY_THUMBNAIL) { mesh_library_palette->set_max_columns(0); mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_TOP); - mesh_library_palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1.5)); + mesh_library_palette->set_fixed_column_width(min_size * MAX(zoom_widget->get_zoom(), 1.5)); } else if (display_mode == DISPLAY_LIST) { - mesh_library_palette->set_max_columns(1); + mesh_library_palette->set_max_columns(0); mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_LEFT); mesh_library_palette->set_fixed_column_width(0); } @@ -938,6 +1001,11 @@ void GridMapEditor::_update_mesh_library() { } update_palette(); + // Make sure we select the first tile as default possible. + if (mesh_library_palette->get_current() == -1 && mesh_library_palette->get_item_count() > 0) { + mesh_library_palette->set_current(0); + selected_palette = mesh_library_palette->get_item_metadata(0); + } // Update the cursor and grid in case the library is changed or removed. _update_cursor_instance(); update_grid(); @@ -1058,10 +1126,22 @@ void GridMapEditor::_draw_grids(const Vector3 &cell_size) { } void GridMapEditor::_update_theme() { - options->set_button_icon(get_theme_icon(SNAME("GridMap"), EditorStringName(EditorIcons))); + transform_mode_button->set_button_icon(get_theme_icon(SNAME("ToolMove"), EditorStringName(EditorIcons))); + select_mode_button->set_button_icon(get_theme_icon(SNAME("ToolSelect"), EditorStringName(EditorIcons))); + erase_mode_button->set_button_icon(get_theme_icon(SNAME("Eraser"), EditorStringName(EditorIcons))); + paint_mode_button->set_button_icon(get_theme_icon(SNAME("Paint"), EditorStringName(EditorIcons))); + pick_mode_button->set_button_icon(get_theme_icon(SNAME("ColorPick"), EditorStringName(EditorIcons))); + fill_action_button->set_button_icon(get_theme_icon(SNAME("Bucket"), EditorStringName(EditorIcons))); + move_action_button->set_button_icon(get_theme_icon(SNAME("ActionCut"), EditorStringName(EditorIcons))); + duplicate_action_button->set_button_icon(get_theme_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons))); + delete_action_button->set_button_icon(get_theme_icon(SNAME("Clear"), EditorStringName(EditorIcons))); + rotate_x_button->set_button_icon(get_theme_icon(SNAME("RotateLeft"), EditorStringName(EditorIcons))); + rotate_y_button->set_button_icon(get_theme_icon(SNAME("ToolRotate"), EditorStringName(EditorIcons))); + rotate_z_button->set_button_icon(get_theme_icon(SNAME("RotateRight"), EditorStringName(EditorIcons))); search_box->set_right_icon(get_theme_icon(SNAME("Search"), EditorStringName(EditorIcons))); mode_thumbnail->set_button_icon(get_theme_icon(SNAME("FileThumbnail"), EditorStringName(EditorIcons))); mode_list->set_button_icon(get_theme_icon(SNAME("FileList"), EditorStringName(EditorIcons))); + options->set_button_icon(get_theme_icon(SNAME("Tools"), EditorStringName(EditorIcons))); } void GridMapEditor::_notification(int p_what) { @@ -1076,6 +1156,9 @@ void GridMapEditor::_notification(int p_what) { RenderingServer::get_singleton()->instance_set_layer_mask(selection_level_instance[i], 1 << Node3DEditorViewport::MISC_TOOL_LAYER); } + cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); + RenderingServer::get_singleton()->instance_set_layer_mask(cursor_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); + RenderingServer::get_singleton()->instance_set_visible(cursor_instance, false); selection_instance = RenderingServer::get_singleton()->instance_create2(selection_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); RenderingServer::get_singleton()->instance_set_layer_mask(selection_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); paste_instance = RenderingServer::get_singleton()->instance_create2(paste_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); @@ -1097,8 +1180,10 @@ void GridMapEditor::_notification(int p_what) { RenderingServer::get_singleton()->free(selection_level_instance[i]); } + RenderingServer::get_singleton()->free(cursor_instance); RenderingServer::get_singleton()->free(selection_instance); RenderingServer::get_singleton()->free(paste_instance); + cursor_instance = RID(); selection_instance = RID(); paste_instance = RID(); } break; @@ -1144,15 +1229,32 @@ void GridMapEditor::_update_cursor_instance() { } cursor_instance = RID(); - if (selected_palette >= 0) { - if (node && !node->get_mesh_library().is_null()) { + if (mode_buttons_group->get_pressed_button() == paint_mode_button) { + if (selected_palette >= 0 && node && node->get_mesh_library().is_valid()) { Ref<Mesh> mesh = node->get_mesh_library()->get_item_mesh(selected_palette); if (!mesh.is_null() && mesh->get_rid().is_valid()) { cursor_instance = RenderingServer::get_singleton()->instance_create2(mesh->get_rid(), get_tree()->get_root()->get_world_3d()->get_scenario()); - RenderingServer::get_singleton()->instance_set_transform(cursor_instance, cursor_transform); } } + } else if (mode_buttons_group->get_pressed_button() == select_mode_button) { + cursor_inner_mat->set_albedo(Color(default_color, 0.2)); + cursor_outer_mat->set_albedo(Color(default_color, 0.8)); + cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); + } else if (mode_buttons_group->get_pressed_button() == erase_mode_button) { + cursor_inner_mat->set_albedo(Color(erase_color, 0.2)); + cursor_outer_mat->set_albedo(Color(erase_color, 0.8)); + cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); + } else if (mode_buttons_group->get_pressed_button() == pick_mode_button) { + cursor_inner_mat->set_albedo(Color(pick_color, 0.2)); + cursor_outer_mat->set_albedo(Color(pick_color, 0.8)); + cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); } + _update_cursor_transform(); +} + +void GridMapEditor::_on_tool_mode_changed() { + _show_viewports_transform_gizmo(mode_buttons_group->get_pressed_button() == transform_mode_button); + _update_cursor_instance(); } void GridMapEditor::_item_selected_cbk(int idx) { @@ -1182,80 +1284,26 @@ void GridMapEditor::_bind_methods() { } GridMapEditor::GridMapEditor() { - ED_SHORTCUT("grid_map/previous_floor", TTR("Previous Floor"), Key::Q, true); - ED_SHORTCUT("grid_map/next_floor", TTR("Next Floor"), Key::E, true); - ED_SHORTCUT("grid_map/edit_x_axis", TTR("Edit X Axis"), Key::Z, true); - ED_SHORTCUT("grid_map/edit_y_axis", TTR("Edit Y Axis"), Key::X, true); - ED_SHORTCUT("grid_map/edit_z_axis", TTR("Edit Z Axis"), Key::C, true); - ED_SHORTCUT("grid_map/cursor_rotate_x", TTR("Cursor Rotate X"), Key::A, true); - ED_SHORTCUT("grid_map/cursor_rotate_y", TTR("Cursor Rotate Y"), Key::S, true); - ED_SHORTCUT("grid_map/cursor_rotate_z", TTR("Cursor Rotate Z"), Key::D, true); - ED_SHORTCUT("grid_map/cursor_back_rotate_x", TTR("Cursor Back Rotate X"), KeyModifierMask::SHIFT + Key::A, true); - ED_SHORTCUT("grid_map/cursor_back_rotate_y", TTR("Cursor Back Rotate Y"), KeyModifierMask::SHIFT + Key::S, true); - ED_SHORTCUT("grid_map/cursor_back_rotate_z", TTR("Cursor Back Rotate Z"), KeyModifierMask::SHIFT + Key::D, true); - ED_SHORTCUT("grid_map/cursor_clear_rotation", TTR("Cursor Clear Rotation"), Key::W, true); - ED_SHORTCUT("grid_map/paste_selects", TTR("Paste Selects")); - ED_SHORTCUT("grid_map/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::CTRL + Key::C); - ED_SHORTCUT("grid_map/cut_selection", TTR("Cut Selection"), KeyModifierMask::CTRL + Key::X); - ED_SHORTCUT("grid_map/clear_selection", TTR("Clear Selection"), Key::KEY_DELETE); - ED_SHORTCUT("grid_map/fill_selection", TTR("Fill Selection"), KeyModifierMask::CTRL + Key::F); - - int mw = EDITOR_GET("editors/grid_map/palette_min_width"); - Control *ec = memnew(Control); - ec->set_custom_minimum_size(Size2(mw, 0) * EDSCALE); - add_child(ec); - - spatial_editor_hb = memnew(HBoxContainer); - spatial_editor_hb->set_h_size_flags(SIZE_EXPAND_FILL); - spatial_editor_hb->set_alignment(BoxContainer::ALIGNMENT_END); - Node3DEditor::get_singleton()->add_control_to_menu_panel(spatial_editor_hb); - - spin_box_label = memnew(Label); - spin_box_label->set_text(TTR("Floor:")); - spatial_editor_hb->add_child(spin_box_label); - - floor = memnew(SpinBox); - floor->set_min(-32767); - floor->set_max(32767); - floor->set_step(1); - floor->get_line_edit()->add_theme_constant_override("minimum_character_width", 16); - - spatial_editor_hb->add_child(floor); - floor->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_floor_changed)); - floor->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited)); - floor->get_line_edit()->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited)); - - spatial_editor_hb->add_child(memnew(VSeparator)); + ED_SHORTCUT("grid_map/previous_floor", TTR("Previous Floor"), Key::KEY_1, true); + ED_SHORTCUT("grid_map/next_floor", TTR("Next Floor"), Key::KEY_3, true); + ED_SHORTCUT("grid_map/edit_x_axis", TTR("Edit X Axis"), KeyModifierMask::SHIFT + Key::Z, true); + ED_SHORTCUT("grid_map/edit_y_axis", TTR("Edit Y Axis"), KeyModifierMask::SHIFT + Key::X, true); + ED_SHORTCUT("grid_map/edit_z_axis", TTR("Edit Z Axis"), KeyModifierMask::SHIFT + Key::C, true); + ED_SHORTCUT("grid_map/keep_selected", TTR("Keep Selection")); + ED_SHORTCUT("grid_map/clear_rotation", TTR("Clear Rotation")); options = memnew(MenuButton); - spatial_editor_hb->add_child(options); - spatial_editor_hb->hide(); - - options->set_text(TTR("Grid Map")); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/previous_floor"), MENU_OPTION_PREV_LEVEL); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/next_floor"), MENU_OPTION_NEXT_LEVEL); + options->set_theme_type_variation("FlatButton"); options->get_popup()->add_separator(); options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS); options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS); options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS); options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_Y_AXIS), true); options->get_popup()->add_separator(); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_x"), MENU_OPTION_CURSOR_ROTATE_X); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_y"), MENU_OPTION_CURSOR_ROTATE_Y); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_z"), MENU_OPTION_CURSOR_ROTATE_Z); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_x"), MENU_OPTION_CURSOR_BACK_ROTATE_X); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION); - options->get_popup()->add_separator(); // TRANSLATORS: This is a toggle to select after pasting the new content. - options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/paste_selects"), MENU_OPTION_PASTE_SELECTS); - options->get_popup()->add_separator(); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/duplicate_selection"), MENU_OPTION_SELECTION_DUPLICATE); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cut_selection"), MENU_OPTION_SELECTION_CUT); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_selection"), MENU_OPTION_SELECTION_CLEAR); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/fill_selection"), MENU_OPTION_SELECTION_FILL); - + options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION); + options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/keep_selected"), MENU_OPTION_PASTE_SELECTS); + options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_PASTE_SELECTS), true); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS); @@ -1275,40 +1323,180 @@ GridMapEditor::GridMapEditor() { options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GridMapEditor::_menu_option)); - HBoxContainer *hb = memnew(HBoxContainer); - add_child(hb); - hb->set_h_size_flags(SIZE_EXPAND_FILL); + toolbar = memnew(HBoxContainer); + add_child(toolbar); + toolbar->set_h_size_flags(SIZE_EXPAND_FILL); + + HBoxContainer *mode_buttons = memnew(HBoxContainer); + toolbar->add_child(mode_buttons); + mode_buttons_group.instantiate(); + + transform_mode_button = memnew(Button); + transform_mode_button->set_theme_type_variation("FlatButton"); + transform_mode_button->set_toggle_mode(true); + transform_mode_button->set_button_group(mode_buttons_group); + transform_mode_button->set_shortcut(ED_SHORTCUT("grid_map/transform_tool", TTR("Transform"), Key::T, true)); + transform_mode_button->connect(SceneStringName(toggled), + callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1)); + mode_buttons->add_child(transform_mode_button); + viewport_shortcut_buttons.push_back(transform_mode_button); + VSeparator *vsep = memnew(VSeparator); + mode_buttons->add_child(vsep); + + select_mode_button = memnew(Button); + select_mode_button->set_theme_type_variation("FlatButton"); + select_mode_button->set_toggle_mode(true); + select_mode_button->set_button_group(mode_buttons_group); + select_mode_button->set_shortcut(ED_SHORTCUT("grid_map/selection_tool", TTR("Selection"), Key::Q, true)); + select_mode_button->connect(SceneStringName(toggled), + callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1)); + mode_buttons->add_child(select_mode_button); + viewport_shortcut_buttons.push_back(select_mode_button); + select_mode_button->set_pressed(true); + + erase_mode_button = memnew(Button); + erase_mode_button->set_theme_type_variation("FlatButton"); + erase_mode_button->set_toggle_mode(true); + erase_mode_button->set_button_group(mode_buttons_group); + erase_mode_button->set_shortcut(ED_SHORTCUT("grid_map/erase_tool", TTR("Erase"), Key::W, true)); + mode_buttons->add_child(erase_mode_button); + erase_mode_button->connect(SceneStringName(toggled), + callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1)); + viewport_shortcut_buttons.push_back(erase_mode_button); + + paint_mode_button = memnew(Button); + paint_mode_button->set_theme_type_variation("FlatButton"); + paint_mode_button->set_toggle_mode(true); + paint_mode_button->set_button_group(mode_buttons_group); + paint_mode_button->set_shortcut(ED_SHORTCUT("grid_map/paint_tool", TTR("Paint"), Key::E, true)); + paint_mode_button->connect(SceneStringName(toggled), + callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1)); + mode_buttons->add_child(paint_mode_button); + viewport_shortcut_buttons.push_back(paint_mode_button); + + pick_mode_button = memnew(Button); + pick_mode_button->set_theme_type_variation("FlatButton"); + pick_mode_button->set_toggle_mode(true); + pick_mode_button->set_button_group(mode_buttons_group); + pick_mode_button->set_shortcut(ED_SHORTCUT("grid_map/pick_tool", TTR("Pick"), Key::R, true)); + pick_mode_button->connect(SceneStringName(toggled), + callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1)); + mode_buttons->add_child(pick_mode_button); + viewport_shortcut_buttons.push_back(pick_mode_button); + + vsep = memnew(VSeparator); + toolbar->add_child(vsep); + + HBoxContainer *action_buttons = memnew(HBoxContainer); + toolbar->add_child(action_buttons); + + fill_action_button = memnew(Button); + fill_action_button->set_theme_type_variation("FlatButton"); + fill_action_button->set_shortcut(ED_SHORTCUT("grid_map/fill_tool", TTR("Fill"), Key::Z, true)); + fill_action_button->connect(SceneStringName(pressed), + callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_FILL)); + action_buttons->add_child(fill_action_button); + + move_action_button = memnew(Button); + move_action_button->set_theme_type_variation("FlatButton"); + move_action_button->set_shortcut(ED_SHORTCUT("grid_map/move_tool", TTR("Move"), Key::X, true)); + move_action_button->connect(SceneStringName(pressed), + callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_CUT)); + action_buttons->add_child(move_action_button); + + duplicate_action_button = memnew(Button); + duplicate_action_button->set_theme_type_variation("FlatButton"); + duplicate_action_button->set_shortcut(ED_SHORTCUT("grid_map/duplicate_tool", TTR("Duplicate"), Key::C, true)); + duplicate_action_button->connect(SceneStringName(pressed), + callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_DUPLICATE)); + action_buttons->add_child(duplicate_action_button); + + delete_action_button = memnew(Button); + delete_action_button->set_theme_type_variation("FlatButton"); + delete_action_button->set_shortcut(ED_SHORTCUT("grid_map/delete_tool", TTR("Delete"), Key::V, true)); + delete_action_button->connect(SceneStringName(pressed), + callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_CLEAR)); + action_buttons->add_child(delete_action_button); + + vsep = memnew(VSeparator); + toolbar->add_child(vsep); + + HBoxContainer *rotation_buttons = memnew(HBoxContainer); + toolbar->add_child(rotation_buttons); + + rotate_x_button = memnew(Button); + rotate_x_button->set_theme_type_variation("FlatButton"); + rotate_x_button->set_shortcut(ED_SHORTCUT("grid_map/cursor_rotate_x", TTR("Cursor Rotate X"), Key::A, true)); + rotate_x_button->connect(SceneStringName(pressed), + callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_X)); + rotation_buttons->add_child(rotate_x_button); + + rotate_y_button = memnew(Button); + rotate_y_button->set_theme_type_variation("FlatButton"); + rotate_y_button->set_shortcut(ED_SHORTCUT("grid_map/cursor_rotate_y", TTR("Cursor Rotate Y"), Key::S, true)); + rotate_y_button->connect(SceneStringName(pressed), + callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_Y)); + rotation_buttons->add_child(rotate_y_button); + + rotate_z_button = memnew(Button); + rotate_z_button->set_theme_type_variation("FlatButton"); + rotate_z_button->set_shortcut(ED_SHORTCUT("grid_map/cursor_rotate_z", TTR("Cursor Rotate Z"), Key::D, true)); + rotate_z_button->connect(SceneStringName(pressed), + callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_Z)); + rotation_buttons->add_child(rotate_z_button); + + // Wide empty separation control. (like BoxContainer::add_spacer()) + Control *c = memnew(Control); + c->set_mouse_filter(MOUSE_FILTER_PASS); + c->set_h_size_flags(SIZE_EXPAND_FILL); + toolbar->add_child(c); + + floor = memnew(SpinBox); + floor->set_min(-32767); + floor->set_max(32767); + floor->set_step(1); + floor->set_tooltip_text( + TTR(vformat("Change Grid Floor:\nPrevious Plane (%s)\nNext Plane (%s)", + ED_GET_SHORTCUT("grid_map/previous_floor")->get_as_text(), + ED_GET_SHORTCUT("grid_map/next_floor")->get_as_text()))); + toolbar->add_child(floor); + floor->get_line_edit()->add_theme_constant_override("minimum_character_width", 2); + floor->get_line_edit()->set_context_menu_enabled(false); + floor->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_floor_changed)); + floor->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited)); + floor->get_line_edit()->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited)); search_box = memnew(LineEdit); - search_box->set_h_size_flags(SIZE_EXPAND_FILL); + search_box->add_theme_constant_override("minimum_character_width", 10); search_box->set_placeholder(TTR("Filter Meshes")); search_box->set_clear_button_enabled(true); - hb->add_child(search_box); + toolbar->add_child(search_box); search_box->connect(SceneStringName(text_changed), callable_mp(this, &GridMapEditor::_text_changed)); search_box->connect(SceneStringName(gui_input), callable_mp(this, &GridMapEditor::_sbox_input)); + zoom_widget = memnew(EditorZoomWidget); + toolbar->add_child(zoom_widget); + zoom_widget->setup_zoom_limits(0.2, 4); + zoom_widget->set_zoom(1.0); + zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE); + zoom_widget->connect("zoom_changed", callable_mp(this, &GridMapEditor::_icon_size_changed)); + zoom_widget->set_shortcut_context(this); + mode_thumbnail = memnew(Button); mode_thumbnail->set_theme_type_variation("FlatButton"); mode_thumbnail->set_toggle_mode(true); mode_thumbnail->set_pressed(true); - hb->add_child(mode_thumbnail); + toolbar->add_child(mode_thumbnail); mode_thumbnail->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_set_display_mode).bind(DISPLAY_THUMBNAIL)); mode_list = memnew(Button); mode_list->set_theme_type_variation("FlatButton"); mode_list->set_toggle_mode(true); mode_list->set_pressed(false); - hb->add_child(mode_list); + toolbar->add_child(mode_list); mode_list->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_set_display_mode).bind(DISPLAY_LIST)); - size_slider = memnew(HSlider); - size_slider->set_h_size_flags(SIZE_EXPAND_FILL); - size_slider->set_min(0.2f); - size_slider->set_max(4.0f); - size_slider->set_step(0.1f); - size_slider->set_value(1.0f); - size_slider->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_icon_size_changed)); - add_child(size_slider); + toolbar->add_child(options); mesh_library_palette = memnew(ItemList); mesh_library_palette->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); @@ -1330,6 +1518,7 @@ GridMapEditor::GridMapEditor() { edit_floor[1] = -1; edit_floor[2] = -1; + cursor_mesh = RenderingServer::get_singleton()->mesh_create(); selection_mesh = RenderingServer::get_singleton()->mesh_create(); paste_mesh = RenderingServer::get_singleton()->mesh_create(); @@ -1405,20 +1594,32 @@ GridMapEditor::GridMapEditor() { Array d; d.resize(RS::ARRAY_MAX); + default_color = Color(0.0, 0.565, 1.0); // blue 0.7, 0.7, 1.0 + erase_color = Color(1.0, 0.2, 0.2); // red + pick_color = Color(1, 0.7, 0); // orange/yellow + + cursor_inner_mat.instantiate(); + cursor_inner_mat->set_albedo(Color(default_color, 0.2)); + cursor_inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + cursor_inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + cursor_inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + + cursor_outer_mat.instantiate(); + cursor_outer_mat->set_albedo(Color(default_color, 0.8)); + cursor_outer_mat->set_on_top_of_alpha(); + cursor_outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + cursor_outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + cursor_outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + inner_mat.instantiate(); - inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2)); + inner_mat->set_albedo(Color(default_color, 0.2)); inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); - d[RS::ARRAY_VERTEX] = triangles; - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_TRIANGLES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(selection_mesh, 0, inner_mat->get_rid()); - outer_mat.instantiate(); - outer_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.8)); + outer_mat->set_albedo(Color(default_color, 0.8)); outer_mat->set_on_top_of_alpha(); - outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); @@ -1429,6 +1630,18 @@ GridMapEditor::GridMapEditor() { selection_floor_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); selection_floor_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + d[RS::ARRAY_VERTEX] = triangles; + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(cursor_mesh, RS::PRIMITIVE_TRIANGLES, d); + RenderingServer::get_singleton()->mesh_surface_set_material(cursor_mesh, 0, cursor_inner_mat->get_rid()); + + d[RS::ARRAY_VERTEX] = lines; + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(cursor_mesh, RS::PRIMITIVE_LINES, d); + RenderingServer::get_singleton()->mesh_surface_set_material(cursor_mesh, 1, cursor_outer_mat->get_rid()); + + d[RS::ARRAY_VERTEX] = triangles; + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_TRIANGLES, d); + RenderingServer::get_singleton()->mesh_surface_set_material(selection_mesh, 0, inner_mat->get_rid()); + d[RS::ARRAY_VERTEX] = lines; RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_LINES, d); RenderingServer::get_singleton()->mesh_surface_set_material(selection_mesh, 1, outer_mat->get_rid()); @@ -1471,9 +1684,6 @@ GridMapEditor::~GridMapEditor() { if (grid_instance[i].is_valid()) { RenderingServer::get_singleton()->free(grid_instance[i]); } - if (cursor_instance.is_valid()) { - RenderingServer::get_singleton()->free(cursor_instance); - } if (selection_level_instance[i].is_valid()) { RenderingServer::get_singleton()->free(selection_level_instance[i]); } @@ -1482,6 +1692,11 @@ GridMapEditor::~GridMapEditor() { } } + RenderingServer::get_singleton()->free(cursor_mesh); + if (cursor_instance.is_valid()) { + RenderingServer::get_singleton()->free(cursor_instance); + } + RenderingServer::get_singleton()->free(selection_mesh); if (selection_instance.is_valid()) { RenderingServer::get_singleton()->free(selection_instance); @@ -1493,24 +1708,6 @@ GridMapEditor::~GridMapEditor() { } } -void GridMapEditorPlugin::_notification(int p_what) { - switch (p_what) { - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - if (!EditorSettings::get_singleton()->check_changed_settings_in_group("editors/grid_map")) { - break; - } - switch ((int)EDITOR_GET("editors/grid_map/editor_side")) { - case 0: { // Left. - Node3DEditor::get_singleton()->move_control_to_left_panel(grid_map_editor); - } break; - case 1: { // Right. - Node3DEditor::get_singleton()->move_control_to_right_panel(grid_map_editor); - } break; - } - } break; - } -} - void GridMapEditorPlugin::edit(Object *p_object) { grid_map_editor->edit(Object::cast_to<GridMap>(p_object)); } @@ -1521,27 +1718,29 @@ bool GridMapEditorPlugin::handles(Object *p_object) const { void GridMapEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - grid_map_editor->show(); - grid_map_editor->spatial_editor_hb->show(); + grid_map_editor->_on_tool_mode_changed(); + panel_button->show(); + EditorNode::get_bottom_panel()->make_item_visible(grid_map_editor); grid_map_editor->set_process(true); } else { - grid_map_editor->spatial_editor_hb->hide(); - grid_map_editor->hide(); + grid_map_editor->_show_viewports_transform_gizmo(true); + panel_button->hide(); + if (grid_map_editor->is_visible_in_tree()) { + EditorNode::get_bottom_panel()->hide_bottom_panel(); + } grid_map_editor->set_process(false); } } GridMapEditorPlugin::GridMapEditorPlugin() { grid_map_editor = memnew(GridMapEditor); - switch ((int)EDITOR_GET("editors/grid_map/editor_side")) { - case 0: { // Left. - Node3DEditor::get_singleton()->add_control_to_left_panel(grid_map_editor); - } break; - case 1: { // Right. - Node3DEditor::get_singleton()->add_control_to_right_panel(grid_map_editor); - } break; - } + grid_map_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + grid_map_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); + grid_map_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); grid_map_editor->hide(); + + panel_button = EditorNode::get_bottom_panel()->add_item(TTR("GridMap"), grid_map_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_grid_map_bottom_panel", TTR("Toggle GridMap Bottom Panel"))); + panel_button->hide(); } GridMapEditorPlugin::~GridMapEditorPlugin() { diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h index 4294c93c93..2d43a5c830 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.h +++ b/modules/gridmap/editor/grid_map_editor_plugin.h @@ -44,6 +44,9 @@ class ConfirmationDialog; class MenuButton; class Node3DEditorPlugin; +class ButtonGroup; +class EditorZoomWidget; +class BaseButton; class GridMapEditor : public VBoxContainer { GDCLASS(GridMapEditor, VBoxContainer); @@ -54,6 +57,7 @@ class GridMapEditor : public VBoxContainer { enum InputAction { INPUT_NONE, + INPUT_TRANSFORM, INPUT_PAINT, INPUT_ERASE, INPUT_PICK, @@ -71,11 +75,31 @@ class GridMapEditor : public VBoxContainer { MenuButton *options = nullptr; SpinBox *floor = nullptr; double accumulated_floor_delta = 0.0; + + HBoxContainer *toolbar = nullptr; + List<BaseButton *> viewport_shortcut_buttons; + Ref<ButtonGroup> mode_buttons_group; + // mode + Button *transform_mode_button = nullptr; + Button *select_mode_button = nullptr; + Button *erase_mode_button = nullptr; + Button *paint_mode_button = nullptr; + Button *pick_mode_button = nullptr; + // action + Button *fill_action_button = nullptr; + Button *move_action_button = nullptr; + Button *duplicate_action_button = nullptr; + Button *delete_action_button = nullptr; + // rotation + Button *rotate_x_button = nullptr; + Button *rotate_y_button = nullptr; + Button *rotate_z_button = nullptr; + + EditorZoomWidget *zoom_widget = nullptr; Button *mode_thumbnail = nullptr; Button *mode_list = nullptr; LineEdit *search_box = nullptr; HSlider *size_slider = nullptr; - HBoxContainer *spatial_editor_hb = nullptr; ConfirmationDialog *settings_dialog = nullptr; VBoxContainer *settings_vbc = nullptr; SpinBox *settings_pick_distance = nullptr; @@ -102,6 +126,7 @@ class GridMapEditor : public VBoxContainer { RID grid[3]; RID grid_instance[3]; + RID cursor_mesh; RID cursor_instance; RID selection_mesh; RID selection_instance; @@ -119,7 +144,12 @@ class GridMapEditor : public VBoxContainer { List<ClipboardItem> clipboard_items; + Color default_color; + Color erase_color; + Color pick_color; Ref<StandardMaterial3D> indicator_mat; + Ref<StandardMaterial3D> cursor_inner_mat; + Ref<StandardMaterial3D> cursor_outer_mat; Ref<StandardMaterial3D> inner_mat; Ref<StandardMaterial3D> outer_mat; Ref<StandardMaterial3D> selection_floor_mat; @@ -196,6 +226,7 @@ class GridMapEditor : public VBoxContainer { void _item_selected_cbk(int idx); void _update_cursor_transform(); void _update_cursor_instance(); + void _on_tool_mode_changed(); void _update_theme(); void _text_changed(const String &p_text); @@ -208,6 +239,7 @@ class GridMapEditor : public VBoxContainer { void _set_clipboard_data(); void _update_paste_indicator(); void _do_paste(); + void _show_viewports_transform_gizmo(bool p_value); void _update_selection_transform(); void _validate_selection(); void _set_selection(bool p_active, const Vector3 &p_begin = Vector3(), const Vector3 &p_end = Vector3()); @@ -238,9 +270,7 @@ class GridMapEditorPlugin : public EditorPlugin { GDCLASS(GridMapEditorPlugin, EditorPlugin); GridMapEditor *grid_map_editor = nullptr; - -protected: - void _notification(int p_what); + Button *panel_button = nullptr; public: virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 71171be3f1..0588ba034a 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -801,8 +801,8 @@ void GridMap::_octant_enter_world(const OctantKey &p_key) { if (!g.navigation_debug_edge_connections_instance.is_valid()) { g.navigation_debug_edge_connections_instance = RenderingServer::get_singleton()->instance_create(); } - if (!g.navigation_debug_edge_connections_mesh.is_valid()) { - g.navigation_debug_edge_connections_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); + if (g.navigation_debug_edge_connections_mesh.is_null()) { + g.navigation_debug_edge_connections_mesh.instantiate(); } _update_octant_navigation_debug_edge_connections_mesh(p_key); @@ -1386,8 +1386,8 @@ void GridMap::_update_octant_navigation_debug_edge_connections_mesh(const Octant g.navigation_debug_edge_connections_instance = RenderingServer::get_singleton()->instance_create(); } - if (!g.navigation_debug_edge_connections_mesh.is_valid()) { - g.navigation_debug_edge_connections_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); + if (g.navigation_debug_edge_connections_mesh.is_null()) { + g.navigation_debug_edge_connections_mesh.instantiate(); } g.navigation_debug_edge_connections_mesh->clear_surfaces(); diff --git a/modules/minimp3/resource_importer_mp3.cpp b/modules/minimp3/resource_importer_mp3.cpp index e4b54ef050..f1f0a771ad 100644 --- a/modules/minimp3/resource_importer_mp3.cpp +++ b/modules/minimp3/resource_importer_mp3.cpp @@ -115,7 +115,7 @@ Ref<AudioStreamMP3> ResourceImporterMP3::import_mp3(const String &p_path) { return mp3_stream; } -Error ResourceImporterMP3::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { +Error ResourceImporterMP3::import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { bool loop = p_options["loop"]; float loop_offset = p_options["loop_offset"]; double bpm = p_options["bpm"]; diff --git a/modules/minimp3/resource_importer_mp3.h b/modules/minimp3/resource_importer_mp3.h index 037756328f..35cc761eb4 100644 --- a/modules/minimp3/resource_importer_mp3.h +++ b/modules/minimp3/resource_importer_mp3.h @@ -57,7 +57,7 @@ public: #endif static Ref<AudioStreamMP3> import_mp3(const String &p_path); - virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; virtual bool can_import_threaded() const override { return true; } diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 6ae9ce56c6..380b401683 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -2826,7 +2826,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr); ERR_FAIL_COND_V_MSG(scr.is_null(), Ref<Resource>(), "Could not create C# script '" + real_path + "'."); } else { - scr = Ref<CSharpScript>(memnew(CSharpScript)); + scr.instantiate(); } #if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs index f54058b0d9..5af859c06b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs @@ -32,9 +32,9 @@ partial class EventSignals add => backing_MySignal += value; remove => backing_MySignal -= value; } - protected void EmitSignalMySignal(string str, int num) + protected void EmitSignalMySignal(string @str, int @num) { - EmitSignal(SignalName.MySignal, str, num); + EmitSignal(SignalName.MySignal, @str, @num); } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 702c50d461..c7a7415851 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -295,7 +295,7 @@ namespace Godot.SourceGenerators for (int i = 0; i < paramCount; i++) { var paramSymbol = invokeMethodSymbol.Parameters[i]; - source.Append($"{paramSymbol.Type.FullQualifiedNameIncludeGlobal()} {paramSymbol.Name}"); + source.Append($"{paramSymbol.Type.FullQualifiedNameIncludeGlobal()} @{paramSymbol.Name}"); if (i < paramCount - 1) { source.Append(", "); @@ -310,11 +310,11 @@ namespace Godot.SourceGenerators if (paramSymbol.Type.TypeKind == TypeKind.Enum) { var underlyingType = ((INamedTypeSymbol)paramSymbol.Type).EnumUnderlyingType; - source.Append($", ({underlyingType.FullQualifiedNameIncludeGlobal()}){paramSymbol.Name}"); + source.Append($", ({underlyingType.FullQualifiedNameIncludeGlobal()})@{paramSymbol.Name}"); continue; } - source.Append($", {paramSymbol.Name}"); + source.Append($", @{paramSymbol.Name}"); } source.Append(");\n"); source.Append(" }\n"); diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 788b46ab9a..74e04b46a1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -177,7 +177,7 @@ namespace GodotTools private static readonly string[] VsCodeNames = { - "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss" + "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss", "codium" }; [UsedImplicitly] @@ -330,7 +330,7 @@ namespace GodotTools args.Add("-b"); args.Add(vscodeBundleId); - // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is + // The reusing of existing windows made by the 'open' command might not choose a window that is // editing our folder. It's better to ask for a new window and let VSCode do the window management. args.Add("-n"); @@ -339,6 +339,28 @@ namespace GodotTools args.Add("--args"); } + + // Try VSCodium as a fallback if Visual Studio Code can't be found. + if (!macOSAppBundleInstalled) + { + const string VscodiumBundleId = "com.vscodium.codium"; + macOSAppBundleInstalled = Internal.IsMacOSAppBundleInstalled(VscodiumBundleId); + + if (macOSAppBundleInstalled) + { + args.Add("-b"); + args.Add(VscodiumBundleId); + + // The reusing of existing windows made by the 'open' command might not choose a window that is + // editing our folder. It's better to ask for a new window and let VSCode do the window management. + args.Add("-n"); + + // The open process must wait until the application finishes (which is instant in VSCode's case) + args.Add("--wait-apps"); + + args.Add("--args"); + } + } } args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)!); @@ -361,7 +383,7 @@ namespace GodotTools { if (!macOSAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath)) { - GD.PushError("Cannot find code editor: VSCode"); + GD.PushError("Cannot find code editor: Visual Studio Code or VSCodium"); return Error.FileNotFound; } @@ -371,7 +393,7 @@ namespace GodotTools { if (string.IsNullOrEmpty(_vsCodePath)) { - GD.PushError("Cannot find code editor: VSCode"); + GD.PushError("Cannot find code editor: Visual Studio Code or VSCodium"); return Error.FileNotFound; } @@ -384,7 +406,7 @@ namespace GodotTools } catch (Exception e) { - GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'"); + GD.PushError($"Error when trying to run code editor: Visual Studio Code or VSCodium. Exception message: '{e.Message}'"); } break; @@ -550,7 +572,7 @@ namespace GodotTools { settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" + $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + - $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + + $",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" + $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" + $",Custom:{(int)ExternalEditorId.CustomEditor}"; } @@ -558,14 +580,14 @@ namespace GodotTools { settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" + $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + - $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + + $",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" + $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" + $",Custom:{(int)ExternalEditorId.CustomEditor}"; } else if (OS.IsUnixLike) { settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + - $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + + $",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" + $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" + $",Custom:{(int)ExternalEditorId.CustomEditor}"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs index dc151e2c3e..222ded6895 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -534,7 +534,10 @@ namespace Godot.NativeInterop [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Callable ConvertToCallable(in godot_variant p_var) - => Marshaling.ConvertCallableToManaged(ConvertToNativeCallable(p_var)); + { + using var callable = ConvertToNativeCallable(p_var); + return Marshaling.ConvertCallableToManaged(callable); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static godot_signal ConvertToNativeSignal(in godot_variant p_var) @@ -542,7 +545,10 @@ namespace Godot.NativeInterop [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Signal ConvertToSignal(in godot_variant p_var) - => Marshaling.ConvertSignalToManaged(ConvertToNativeSignal(p_var)); + { + using var signal = ConvertToNativeSignal(p_var); + return Marshaling.ConvertSignalToManaged(signal); + } public static godot_array ConvertToNativeArray(in godot_variant p_var) => p_var.Type == Variant.Type.Array ? diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index e245101eeb..dde14034e6 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -684,9 +684,9 @@ void SceneMultiplayer::_bind_methods() { SceneMultiplayer::SceneMultiplayer() { relay_buffer.instantiate(); - cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this))); - replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this, cache.ptr()))); - rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this, cache.ptr(), replicator.ptr()))); + cache.instantiate(this); + replicator.instantiate(this, cache.ptr()); + rpc.instantiate(this, cache.ptr(), replicator.ptr()); set_multiplayer_peer(Ref<OfflineMultiplayerPeer>(memnew(OfflineMultiplayerPeer))); } diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index 0938d7ef99..b5f3889268 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -73,16 +73,6 @@ int get_packet_len(uint32_t p_node_target, int p_packet_len) { } } -bool SceneRPCInterface::_sort_rpc_names(const Variant &p_l, const Variant &p_r) { - if (likely(p_l.is_string() && p_r.is_string())) { - return p_l.operator String() < p_r.operator String(); - } - bool valid = false; - Variant res; - Variant::evaluate(Variant::OP_LESS, p_l, p_r, res, valid); - return valid ? res.operator bool() : false; -} - void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_node, RPCConfigCache &r_cache) { if (p_config.get_type() == Variant::NIL) { return; @@ -90,7 +80,7 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no ERR_FAIL_COND(p_config.get_type() != Variant::DICTIONARY); const Dictionary config = p_config; Array names = config.keys(); - names.sort_custom(callable_mp_static(&SceneRPCInterface::_sort_rpc_names)); // Ensure ID order + names.sort_custom(callable_mp_static(&StringLikeVariantOrder::compare)); // Ensure ID order for (int i = 0; i < names.size(); i++) { ERR_CONTINUE(!names[i].is_string()); String name = names[i].operator String(); diff --git a/modules/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h index 852cef7830..5c9b66d5f5 100644 --- a/modules/multiplayer/scene_rpc_interface.h +++ b/modules/multiplayer/scene_rpc_interface.h @@ -91,8 +91,6 @@ private: #endif protected: - static bool _sort_rpc_names(const Variant &p_l, const Variant &p_r); - void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 16cef0dd34..c996c9e935 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -691,11 +691,15 @@ void NavMeshGenerator2D::generator_parse_navigationobstacle_node(const Ref<Navig return; } - const Transform2D node_xform = p_source_geometry_data->root_node_transform * Transform2D(0.0, obstacle->get_global_position()); - + const Vector2 safe_scale = obstacle->get_global_scale().abs().maxf(0.001); const float obstacle_radius = obstacle->get_radius(); if (obstacle_radius > 0.0) { + // Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis. + const float scaling_max_value = safe_scale[safe_scale.max_axis_index()]; + const Vector2 uniform_max_scale = Vector2(scaling_max_value, scaling_max_value); + const Transform2D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform2D(obstacle->get_global_rotation(), uniform_max_scale, 0.0, obstacle->get_global_position()); + Vector<Vector2> obstruction_circle_vertices; // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. @@ -709,12 +713,15 @@ void NavMeshGenerator2D::generator_parse_navigationobstacle_node(const Ref<Navig for (int i = 0; i < circle_points; i++) { const float angle = i * circle_point_step; - circle_vertices_ptrw[i] = node_xform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius)); + circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius)); } p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_carve_navigation_mesh()); } + // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account. + const Transform2D node_xform = p_source_geometry_data->root_node_transform * obstacle->get_global_transform(); + const Vector<Vector2> &obstacle_vertices = obstacle->get_vertices(); if (obstacle_vertices.is_empty()) { @@ -760,16 +767,14 @@ void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPoly static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPathD *p_polypath_item) { using namespace Clipper2Lib; - Vector<Vector2> polygon_vertices; + TPPLPoly tp; + int size = p_polypath_item->Polygon().size(); + tp.Init(size); + int j = 0; for (const PointD &polypath_point : p_polypath_item->Polygon()) { - polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y))); - } - - TPPLPoly tp; - tp.Init(polygon_vertices.size()); - for (int j = 0; j < polygon_vertices.size(); j++) { - tp[j] = polygon_vertices[j]; + tp[j] = Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y)); + ++j; } if (p_polypath_item->IsHole()) { @@ -842,87 +847,79 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation return; } - if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { - return; - } - - int outline_count = p_navigation_mesh->get_outline_count(); - - Vector<Vector<Vector2>> traversable_outlines; - Vector<Vector<Vector2>> obstruction_outlines; - Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> projected_obstructions; - - p_source_geometry_data->get_data( - traversable_outlines, - obstruction_outlines, - projected_obstructions); - - if (outline_count == 0 && traversable_outlines.size() == 0) { - return; - } - using namespace Clipper2Lib; - PathsD traversable_polygon_paths; PathsD obstruction_polygon_paths; + int obstruction_polygon_path_size = 0; + { + RWLockRead read_lock(p_source_geometry_data->geometry_rwlock); - traversable_polygon_paths.reserve(outline_count + traversable_outlines.size()); - obstruction_polygon_paths.reserve(obstruction_outlines.size()); + const Vector<Vector<Vector2>> &traversable_outlines = p_source_geometry_data->traversable_outlines; + int outline_count = p_navigation_mesh->get_outline_count(); - for (int i = 0; i < outline_count; i++) { - const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i); - PathD subject_path; - subject_path.reserve(traversable_outline.size()); - for (const Vector2 &traversable_point : traversable_outline) { - const PointD &point = PointD(traversable_point.x, traversable_point.y); - subject_path.push_back(point); + if (outline_count == 0 && (!p_source_geometry_data->has_data() || (traversable_outlines.is_empty()))) { + return; } - traversable_polygon_paths.push_back(subject_path); - } - for (const Vector<Vector2> &traversable_outline : traversable_outlines) { - PathD subject_path; - subject_path.reserve(traversable_outline.size()); - for (const Vector2 &traversable_point : traversable_outline) { - const PointD &point = PointD(traversable_point.x, traversable_point.y); - subject_path.push_back(point); - } - traversable_polygon_paths.push_back(subject_path); - } + const Vector<Vector<Vector2>> &obstruction_outlines = p_source_geometry_data->obstruction_outlines; + const Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_projected_obstructions; + + traversable_polygon_paths.reserve(outline_count + traversable_outlines.size()); + obstruction_polygon_paths.reserve(obstruction_outlines.size()); - for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) { - PathD clip_path; - clip_path.reserve(obstruction_outline.size()); - for (const Vector2 &obstruction_point : obstruction_outline) { - const PointD &point = PointD(obstruction_point.x, obstruction_point.y); - clip_path.push_back(point); + for (int i = 0; i < outline_count; i++) { + const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i); + PathD subject_path; + subject_path.reserve(traversable_outline.size()); + for (const Vector2 &traversable_point : traversable_outline) { + subject_path.emplace_back(traversable_point.x, traversable_point.y); + } + traversable_polygon_paths.push_back(std::move(subject_path)); } - obstruction_polygon_paths.push_back(clip_path); - } - if (!projected_obstructions.is_empty()) { - for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { - if (projected_obstruction.carve) { - continue; + for (const Vector<Vector2> &traversable_outline : traversable_outlines) { + PathD subject_path; + subject_path.reserve(traversable_outline.size()); + for (const Vector2 &traversable_point : traversable_outline) { + subject_path.emplace_back(traversable_point.x, traversable_point.y); } - if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) { - continue; + traversable_polygon_paths.push_back(std::move(subject_path)); + } + + if (!projected_obstructions.is_empty()) { + for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) { + continue; + } + + PathD clip_path; + clip_path.reserve(projected_obstruction.vertices.size() / 2); + for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { + clip_path.emplace_back(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + } + if (!IsPositive(clip_path)) { + std::reverse(clip_path.begin(), clip_path.end()); + } + obstruction_polygon_paths.push_back(std::move(clip_path)); } + } + obstruction_polygon_path_size = obstruction_polygon_paths.size(); + for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) { PathD clip_path; - clip_path.reserve(projected_obstruction.vertices.size() / 2); - for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { - const PointD &point = PointD(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); - clip_path.push_back(point); - } - if (!IsPositive(clip_path)) { - std::reverse(clip_path.begin(), clip_path.end()); + clip_path.reserve(obstruction_outline.size()); + for (const Vector2 &obstruction_point : obstruction_outline) { + clip_path.emplace_back(obstruction_point.x, obstruction_point.y); } - obstruction_polygon_paths.push_back(clip_path); + obstruction_polygon_paths.push_back(std::move(clip_path)); } } Rect2 baking_rect = p_navigation_mesh->get_baking_rect(); + PathsD area_obstruction_polygon_paths; if (baking_rect.has_area()) { Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset(); @@ -934,48 +931,27 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation RectD clipper_rect = RectD(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y); traversable_polygon_paths = RectClip(clipper_rect, traversable_polygon_paths); - obstruction_polygon_paths = RectClip(clipper_rect, obstruction_polygon_paths); + area_obstruction_polygon_paths = RectClip(clipper_rect, obstruction_polygon_paths); + } else { + area_obstruction_polygon_paths = obstruction_polygon_paths; } - PathsD path_solution; - // first merge all traversable polygons according to user specified fill rule PathsD dummy_clip_path; traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, FillRule::NonZero); // merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry - obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero); + area_obstruction_polygon_paths = Union(area_obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero); - path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero); + PathsD path_solution = Difference(traversable_polygon_paths, area_obstruction_polygon_paths, FillRule::NonZero); real_t agent_radius_offset = p_navigation_mesh->get_agent_radius(); if (agent_radius_offset > 0.0) { path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon); } - if (!projected_obstructions.is_empty()) { - obstruction_polygon_paths.resize(0); - for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { - if (!projected_obstruction.carve) { - continue; - } - if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) { - continue; - } - - PathD clip_path; - clip_path.reserve(projected_obstruction.vertices.size() / 2); - for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { - const PointD &point = PointD(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); - clip_path.push_back(point); - } - if (!IsPositive(clip_path)) { - std::reverse(clip_path.begin(), clip_path.end()); - } - obstruction_polygon_paths.push_back(clip_path); - } - if (obstruction_polygon_paths.size() > 0) { - path_solution = Difference(path_solution, obstruction_polygon_paths, FillRule::NonZero); - } + if (obstruction_polygon_path_size > 0) { + obstruction_polygon_paths.resize(obstruction_polygon_path_size); + path_solution = Difference(path_solution, obstruction_polygon_paths, FillRule::NonZero); } //path_solution = RamerDouglasPeucker(path_solution, 0.025); // @@ -994,33 +970,11 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation path_solution = RectClip(clipper_rect, path_solution); } - Vector<Vector<Vector2>> new_baked_outlines; - - for (const PathD &scaled_path : path_solution) { - Vector<Vector2> polypath; - for (const PointD &scaled_point : scaled_path) { - polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y))); - } - new_baked_outlines.push_back(polypath); - } - - if (new_baked_outlines.size() == 0) { + if (path_solution.size() == 0) { p_navigation_mesh->clear(); return; } - PathsD polygon_paths; - polygon_paths.reserve(new_baked_outlines.size()); - - for (const Vector<Vector2> &baked_outline : new_baked_outlines) { - PathD polygon_path; - for (const Vector2 &baked_outline_point : baked_outline) { - const PointD &point = PointD(baked_outline_point.x, baked_outline_point.y); - polygon_path.push_back(point); - } - polygon_paths.push_back(polygon_path); - } - ClipType clipper_cliptype = ClipType::Union; List<TPPLPoly> tppl_in_polygon, tppl_out_polygon; @@ -1028,7 +982,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation PolyTreeD polytree; ClipperD clipper_D; - clipper_D.AddSubject(polygon_paths); + clipper_D.AddSubject(path_solution); clipper_D.Execute(clipper_cliptype, FillRule::NonZero, polytree); for (size_t i = 0; i < polytree.Count(); i++) { diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index ce1551e584..3d0697a7cf 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -595,11 +595,17 @@ void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref<Navig return; } - const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis(), obstacle->get_global_position()); - + const float elevation = obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y; + // Prevent non-positive scaling. + const Vector3 safe_scale = obstacle->get_global_basis().get_scale().abs().maxf(0.001); const float obstacle_radius = obstacle->get_radius(); if (obstacle_radius > 0.0) { + // Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis. + const float scaling_max_value = safe_scale[safe_scale.max_axis_index()]; + const Vector3 uniform_max_scale = Vector3(scaling_max_value, scaling_max_value, scaling_max_value); + const Transform3D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(uniform_max_scale), obstacle->get_global_position()); + Vector<Vector3> obstruction_circle_vertices; // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. @@ -613,12 +619,15 @@ void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref<Navig for (int i = 0; i < circle_points; i++) { const float angle = i * circle_point_step; - circle_vertices_ptrw[i] = node_xform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius)); + circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius)); } - p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y - obstacle_radius, obstacle_radius, obstacle->get_carve_navigation_mesh()); + p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, elevation - obstacle_radius, scaling_max_value * obstacle_radius, obstacle->get_carve_navigation_mesh()); } + // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account. + const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), obstacle->get_global_rotation().y), obstacle->get_global_position()); + const Vector<Vector3> &obstacle_vertices = obstacle->get_vertices(); if (obstacle_vertices.is_empty()) { @@ -635,7 +644,7 @@ void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref<Navig obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); obstruction_shape_vertices_ptrw[i].y = 0.0; } - p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y, obstacle->get_height(), obstacle->get_carve_navigation_mesh()); + p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, elevation, safe_scale.y * obstacle->get_height(), obstacle->get_carve_navigation_mesh()); } void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node) { diff --git a/modules/noise/doc_classes/FastNoiseLite.xml b/modules/noise/doc_classes/FastNoiseLite.xml index 6f6a637893..e29581693b 100644 --- a/modules/noise/doc_classes/FastNoiseLite.xml +++ b/modules/noise/doc_classes/FastNoiseLite.xml @@ -118,7 +118,7 @@ Manhattan distance (taxicab metric) to the nearest point. </constant> <constant name="DISTANCE_HYBRID" value="3" enum="CellularDistanceFunction"> - Blend of [constant DISTANCE_EUCLIDEAN] and [constant DISTANCE_MANHATTAN] to give curved cell boundaries + Blend of [constant DISTANCE_EUCLIDEAN] and [constant DISTANCE_MANHATTAN] to give curved cell boundaries. </constant> <constant name="RETURN_CELL_VALUE" value="0" enum="CellularReturnType"> The cellular distance function will return the same value for all points within a cell. diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp index 2d29b8a82c..1e3490d1ed 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp +++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp @@ -281,7 +281,7 @@ void OpenXRViewportCompositionLayerProvider::create_android_surface() { composition_layer_extension->create_android_surface_swapchain(&info, &android_surface.swapchain, &surface); if (surface) { - android_surface.surface = Ref<JavaObject>(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap("android.view.Surface"), surface))); + android_surface.surface.instantiate(JavaClassWrapper::get_singleton()->wrap("android.view.Surface"), surface); } } #endif diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml index e12dc43b6f..66e7cd5a0b 100644 --- a/modules/regex/doc_classes/RegEx.xml +++ b/modules/regex/doc_classes/RegEx.xml @@ -34,14 +34,14 @@ print(result.get_string("digit")) # Would print 01 03 0 3f 42 [/codeblock] - [b]Example of splitting a string using a RegEx:[/b] + [b]Example:[/b] Split a string using a RegEx: [codeblock] var regex = RegEx.new() regex.compile("\\S+") # Negated whitespace character class. var results = [] for result in regex.search_all("One Two \n\tThree"): results.push_back(result.get_string()) - # The `results` array now contains "One", "Two", "Three". + # The `results` array now contains "One", "Two", and "Three". [/codeblock] [b]Note:[/b] Godot's regex implementation is based on the [url=https://www.pcre.org/]PCRE2[/url] library. You can view the full pattern reference [url=https://www.pcre.org/current/doc/html/pcre2pattern.html]here[/url]. [b]Tip:[/b] You can use [url=https://regexr.com/]Regexr[/url] to test regular expressions online. diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 8b2c58acd5..372885b0b4 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -629,7 +629,7 @@ void VideoStreamPlaybackTheora::_streaming_thread(void *ud) { #endif VideoStreamPlaybackTheora::VideoStreamPlaybackTheora() { - texture = Ref<ImageTexture>(memnew(ImageTexture)); + texture.instantiate(); #ifdef THEORA_USE_THREAD_STREAMING int rb_power = nearest_shift(RB_SIZE_KB * 1024); diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp index 729a6f5561..a7423e2d7b 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp @@ -95,7 +95,7 @@ void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) { } #endif -Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { +Error ResourceImporterOggVorbis::import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { bool loop = p_options["loop"]; double loop_offset = p_options["loop_offset"]; double bpm = p_options["bpm"]; diff --git a/modules/vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h index f378b80694..a4e4441d82 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.h +++ b/modules/vorbis/resource_importer_ogg_vorbis.h @@ -63,7 +63,7 @@ public: virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override; virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override; - virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; virtual bool can_import_threaded() const override { return true; } diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 238dd30536..59a2e2ac1a 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -155,6 +155,10 @@ The extra HTTP headers to be sent during the WebSocket handshake. [b]Note:[/b] Not supported in Web exports due to browsers' restrictions. </member> + <member name="heartbeat_interval" type="float" setter="set_heartbeat_interval" getter="get_heartbeat_interval" default="0.0"> + The interval (in seconds) at which the peer will automatically send WebSocket "ping" control frames. When set to [code]0[/code], no "ping" control frames will be sent. + [b]Note:[/b] Has no effect in Web exports due to browser restrictions. + </member> <member name="inbound_buffer_size" type="int" setter="set_inbound_buffer_size" getter="get_inbound_buffer_size" default="65535"> The size of the input buffer in bytes (roughly the maximum amount of memory that will be allocated for the inbound packets). </member> diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp index 95a1a238e9..5c24b5d082 100644 --- a/modules/websocket/websocket_peer.cpp +++ b/modules/websocket/websocket_peer.cpp @@ -70,6 +70,9 @@ void WebSocketPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_max_queued_packets", "buffer_size"), &WebSocketPeer::set_max_queued_packets); ClassDB::bind_method(D_METHOD("get_max_queued_packets"), &WebSocketPeer::get_max_queued_packets); + ClassDB::bind_method(D_METHOD("set_heartbeat_interval", "interval"), &WebSocketPeer::set_heartbeat_interval); + ClassDB::bind_method(D_METHOD("get_heartbeat_interval"), &WebSocketPeer::get_heartbeat_interval); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "supported_protocols"), "set_supported_protocols", "get_supported_protocols"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "handshake_headers"), "set_handshake_headers", "get_handshake_headers"); @@ -78,6 +81,8 @@ void WebSocketPeer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "max_queued_packets"), "set_max_queued_packets", "get_max_queued_packets"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "heartbeat_interval"), "set_heartbeat_interval", "get_heartbeat_interval"); + BIND_ENUM_CONSTANT(WRITE_MODE_TEXT); BIND_ENUM_CONSTANT(WRITE_MODE_BINARY); @@ -151,3 +156,12 @@ void WebSocketPeer::set_max_queued_packets(int p_max_queued_packets) { int WebSocketPeer::get_max_queued_packets() const { return max_queued_packets; } + +double WebSocketPeer::get_heartbeat_interval() const { + return heartbeat_interval_msec / 1000.0; +} + +void WebSocketPeer::set_heartbeat_interval(double p_interval) { + ERR_FAIL_COND(p_interval < 0); + heartbeat_interval_msec = p_interval * 1000.0; +} diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index ef0197cf6c..3696e787e1 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -72,6 +72,7 @@ protected: int outbound_buffer_size = DEFAULT_BUFFER_SIZE; int inbound_buffer_size = DEFAULT_BUFFER_SIZE; int max_queued_packets = 2048; + uint64_t heartbeat_interval_msec = 0; public: static WebSocketPeer *create(bool p_notify_postinitialize = true) { @@ -117,6 +118,9 @@ public: void set_max_queued_packets(int p_max_queued_packets); int get_max_queued_packets() const; + double get_heartbeat_interval() const; + void set_heartbeat_interval(double p_interval); + WebSocketPeer(); ~WebSocketPeer(); }; diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 0c0a046805..b624da09e8 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -636,7 +636,10 @@ void WSLPeer::_wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct w uint8_t is_string = arg->opcode == WSLAY_TEXT_FRAME ? 1 : 0; peer->in_buffer.write_packet(arg->msg, arg->msg_length, &is_string); } - // Ping or pong. + if (op == WSLAY_PONG) { + peer->heartbeat_waiting = false; + } + // Pong. } wslay_event_callbacks WSLPeer::_wsl_callbacks = { @@ -680,7 +683,31 @@ void WSLPeer::poll() { if (ready_state == STATE_OPEN || ready_state == STATE_CLOSING) { ERR_FAIL_NULL(wsl_ctx); + uint64_t ticks = OS::get_singleton()->get_ticks_msec(); int err = 0; + if (heartbeat_interval_msec != 0 && ticks - last_heartbeat > heartbeat_interval_msec && ready_state == STATE_OPEN) { + if (heartbeat_waiting) { + wslay_event_context_free(wsl_ctx); + wsl_ctx = nullptr; + close(-1); + return; + } + heartbeat_waiting = true; + struct wslay_event_msg msg; + msg.opcode = WSLAY_PING; + msg.msg = nullptr; + msg.msg_length = 0; + err = wslay_event_queue_msg(wsl_ctx, &msg); + if (err == 0) { + last_heartbeat = ticks; + } else { + print_verbose("Websocket (wslay) failed to send ping: " + itos(err)); + wslay_event_context_free(wsl_ctx); + wsl_ctx = nullptr; + close(-1); + return; + } + } if ((err = wslay_event_recv(wsl_ctx)) != 0 || (err = wslay_event_send(wsl_ctx)) != 0) { // Error close. print_verbose("Websocket (wslay) poll error: " + itos(err)); @@ -689,12 +716,37 @@ void WSLPeer::poll() { close(-1); return; } - if (wslay_event_get_close_sent(wsl_ctx) && wslay_event_get_close_received(wsl_ctx)) { - // Clean close. - wslay_event_context_free(wsl_ctx); - wsl_ctx = nullptr; - close(-1); - return; + if (wslay_event_get_close_sent(wsl_ctx)) { + if (wslay_event_get_close_received(wsl_ctx)) { + // Clean close. + wslay_event_context_free(wsl_ctx); + wsl_ctx = nullptr; + close(-1); + return; + } else if (!wslay_event_get_read_enabled(wsl_ctx)) { + // Some protocol error caused wslay to stop processing incoming events, we'll never receive a close from the other peer. + close_code = wslay_event_get_status_code_sent(wsl_ctx); + switch (close_code) { + case WSLAY_CODE_MESSAGE_TOO_BIG: + close_reason = "Message too big"; + break; + case WSLAY_CODE_PROTOCOL_ERROR: + close_reason = "Protocol error"; + break; + case WSLAY_CODE_ABNORMAL_CLOSURE: + close_reason = "Abnormal closure"; + break; + case WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA: + close_reason = "Invalid frame payload data"; + break; + default: + close_reason = "Unknown"; + } + wslay_event_context_free(wsl_ctx); + wsl_ctx = nullptr; + close(-1); + return; + } } } } @@ -781,6 +833,7 @@ void WSLPeer::close(int p_code, String p_reason) { } } + heartbeat_waiting = false; in_buffer.clear(); packet_buffer.resize(0); } diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h index c4fe18630c..07bd850607 100644 --- a/modules/websocket/wsl_peer.h +++ b/modules/websocket/wsl_peer.h @@ -99,6 +99,8 @@ private: int close_code = -1; String close_reason; uint8_t was_string = 0; + uint64_t last_heartbeat = 0; + bool heartbeat_waiting = false; // WebSocket configuration. bool use_tls = true; |
