|
@@ -32,18 +32,27 @@
|
|
|
|
|
|
#include "editor/editor_node.h"
|
|
|
#include "editor/editor_string_names.h"
|
|
|
+#include "editor/multi_node_edit.h"
|
|
|
#include "editor/plugins/node_3d_editor_plugin.h"
|
|
|
#include "scene/3d/navigation/navigation_region_3d.h"
|
|
|
#include "scene/gui/box_container.h"
|
|
|
#include "scene/gui/button.h"
|
|
|
#include "scene/gui/dialogs.h"
|
|
|
#include "scene/gui/label.h"
|
|
|
+#include "servers/navigation_server_3d.h"
|
|
|
|
|
|
void NavigationRegion3DEditor::_node_removed(Node *p_node) {
|
|
|
- if (p_node == node) {
|
|
|
- node = nullptr;
|
|
|
+ if (selected_regions.is_empty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- hide();
|
|
|
+ NavigationRegion3D *region = Object::cast_to<NavigationRegion3D>(p_node);
|
|
|
+
|
|
|
+ if (region && selected_regions.has(region)) {
|
|
|
+ selected_regions.erase(region);
|
|
|
+ if (selected_regions.is_empty()) {
|
|
|
+ hide();
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -53,73 +62,167 @@ void NavigationRegion3DEditor::_notification(int p_what) {
|
|
|
button_bake->set_button_icon(get_theme_icon(SNAME("Bake"), EditorStringName(EditorIcons)));
|
|
|
button_reset->set_button_icon(get_theme_icon(SNAME("Reload"), EditorStringName(EditorIcons)));
|
|
|
} break;
|
|
|
+ case NOTIFICATION_PROCESS: {
|
|
|
+ if (currently_baking_region) {
|
|
|
+ const String bake_state_msg = NavigationServer3D::get_singleton()->get_baking_navigation_mesh_state_msg(currently_baking_region->get_navigation_mesh());
|
|
|
+ multibake_dialog->set_text(itos(processed_regions_to_bake_count) + " / " + itos(processed_regions_to_bake_count_max) + " - Baking navmesh from region '" + currently_baking_region->get_name() + "'.\n\nBake state: " + bake_state_msg + "\n\nDo NOT change nodes by any means while the baking is parsing the SceneTree.");
|
|
|
+ } else {
|
|
|
+ multibake_dialog->set_text("");
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void NavigationRegion3DEditor::_bake_pressed() {
|
|
|
button_bake->set_pressed(false);
|
|
|
|
|
|
- ERR_FAIL_NULL(node);
|
|
|
- Ref<NavigationMesh> navmesh = node->get_navigation_mesh();
|
|
|
- if (navmesh.is_null()) {
|
|
|
- err_dialog->set_text(TTR("A NavigationMesh resource must be set or created for this node to work."));
|
|
|
- err_dialog->popup_centered();
|
|
|
+ if (selected_regions.is_empty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- String path = navmesh->get_path();
|
|
|
- if (!path.is_resource_file()) {
|
|
|
- int srpos = path.find("::");
|
|
|
- if (srpos != -1) {
|
|
|
- String base = path.substr(0, srpos);
|
|
|
- if (ResourceLoader::get_resource_type(base) == "PackedScene") {
|
|
|
- if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
|
|
|
- err_dialog->set_text(TTR("Cannot generate navigation mesh because it does not belong to the edited scene. Make it unique first."));
|
|
|
- err_dialog->popup_centered();
|
|
|
- return;
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (FileAccess::exists(base + ".import")) {
|
|
|
- err_dialog->set_text(TTR("Cannot generate navigation mesh because it belongs to a resource which was imported."));
|
|
|
- err_dialog->popup_centered();
|
|
|
- return;
|
|
|
+ if (bake_in_process) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ HashSet<Ref<NavigationMesh>> unique_navmeshes;
|
|
|
+ regions_to_bake.clear();
|
|
|
+ regions_with_navmesh_to_bake.clear();
|
|
|
+
|
|
|
+ for (NavigationRegion3D *region : selected_regions) {
|
|
|
+ ERR_CONTINUE(region == nullptr);
|
|
|
+ Ref<NavigationMesh> navmesh = region->get_navigation_mesh();
|
|
|
+ if (navmesh.is_null()) {
|
|
|
+ err_dialog->set_text(TTR("A NavigationMesh resource must be set or created for this node to work."));
|
|
|
+ err_dialog->popup_centered();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String path = navmesh->get_path();
|
|
|
+ if (!path.is_resource_file()) {
|
|
|
+ int srpos = path.find("::");
|
|
|
+ if (srpos != -1) {
|
|
|
+ String base = path.substr(0, srpos);
|
|
|
+ if (ResourceLoader::get_resource_type(base) == "PackedScene") {
|
|
|
+ if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
|
|
|
+ err_dialog->set_text(TTR("Cannot generate navigation mesh because it does not belong to the edited scene. Make it unique first."));
|
|
|
+ err_dialog->popup_centered();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (FileAccess::exists(base + ".import")) {
|
|
|
+ err_dialog->set_text(TTR("Cannot generate navigation mesh because it belongs to a resource which was imported."));
|
|
|
+ err_dialog->popup_centered();
|
|
|
+ return;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ } else {
|
|
|
+ if (FileAccess::exists(path + ".import")) {
|
|
|
+ err_dialog->set_text(TTR("Cannot generate navigation mesh because the resource was imported from another type."));
|
|
|
+ err_dialog->popup_centered();
|
|
|
+ return;
|
|
|
+ }
|
|
|
}
|
|
|
- } else {
|
|
|
- if (FileAccess::exists(path + ".import")) {
|
|
|
- err_dialog->set_text(TTR("Cannot generate navigation mesh because the resource was imported from another type."));
|
|
|
- err_dialog->popup_centered();
|
|
|
- return;
|
|
|
+
|
|
|
+ regions_to_bake.push_back(region);
|
|
|
+
|
|
|
+ if (unique_navmeshes.has(navmesh)) {
|
|
|
+ // No point (re)baking the same resource in case of multi select.
|
|
|
+ // Trying to bake the same navmesh twice would trigger an error.
|
|
|
+ continue;
|
|
|
}
|
|
|
+ unique_navmeshes.insert(navmesh);
|
|
|
+ regions_with_navmesh_to_bake.push_back(region);
|
|
|
}
|
|
|
|
|
|
- node->bake_navigation_mesh(true);
|
|
|
+ if (!regions_with_navmesh_to_bake.is_empty()) {
|
|
|
+ multibake_dialog->set_ok_button_text(TTR("Bake"));
|
|
|
+ multibake_dialog->get_ok_button()->set_disabled(false);
|
|
|
+ multibake_dialog->set_text("Attempting to bake " + itos(regions_with_navmesh_to_bake.size()) + " unique navmesh(es) from " + itos(regions_to_bake.size()) + " selected NavigationRegion3D node(s).\n\nThis can take some time and freeze the Editor temporarily.\n\nDo NOT change nodes by any means while the baking is parsing the SceneTree.");
|
|
|
+ multibake_dialog->popup_centered();
|
|
|
+ if (regions_with_navmesh_to_bake.size() == 1) {
|
|
|
+ // If we only have a single region start bake immediately.
|
|
|
+ _on_navmesh_multibake_confirmed();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void NavigationRegion3DEditor::_on_navmesh_multibake_confirmed() {
|
|
|
+ multibake_dialog->get_ok_button()->set_disabled(true);
|
|
|
+
|
|
|
+ bake_in_process = true;
|
|
|
+ region_baking_canceled = false;
|
|
|
+ processed_regions_to_bake_count = 0;
|
|
|
+ processed_regions_to_bake_count_max = regions_with_navmesh_to_bake.size();
|
|
|
|
|
|
- node->update_gizmos();
|
|
|
+ set_process(true);
|
|
|
+ _process_regions_to_bake();
|
|
|
}
|
|
|
|
|
|
-void NavigationRegion3DEditor::_clear_pressed() {
|
|
|
- if (node) {
|
|
|
- if (node->get_navigation_mesh().is_valid()) {
|
|
|
- node->get_navigation_mesh()->clear();
|
|
|
- }
|
|
|
+void NavigationRegion3DEditor::_process_regions_to_bake() {
|
|
|
+ if (region_baking_canceled) {
|
|
|
+ region_baking_canceled = false;
|
|
|
+ regions_with_navmesh_to_bake.clear();
|
|
|
}
|
|
|
|
|
|
+ if (regions_with_navmesh_to_bake.is_empty()) {
|
|
|
+ regions_to_bake.clear();
|
|
|
+ multibake_dialog->set_visible(false);
|
|
|
+ set_process(false);
|
|
|
+ currently_baking_region = nullptr;
|
|
|
+ bake_in_process = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ NavigationRegion3D *region_to_bake = regions_with_navmesh_to_bake[0];
|
|
|
+ regions_with_navmesh_to_bake.remove_at_unordered(0);
|
|
|
+ processed_regions_to_bake_count += 1;
|
|
|
+ if (region_to_bake && region_to_bake->get_navigation_mesh().is_valid()) {
|
|
|
+ currently_baking_region = region_to_bake;
|
|
|
+ region_to_bake->connect(SNAME("bake_finished"), callable_mp(this, &NavigationRegion3DEditor::_process_regions_to_bake), CONNECT_ONE_SHOT);
|
|
|
+ region_to_bake->bake_navigation_mesh(true);
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ _process_regions_to_bake();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void NavigationRegion3DEditor::_on_navmesh_multibake_canceled() {
|
|
|
+ if (bake_in_process) {
|
|
|
+ region_baking_canceled = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ multibake_dialog->set_visible(false);
|
|
|
+ regions_to_bake.clear();
|
|
|
+ regions_with_navmesh_to_bake.clear();
|
|
|
+ processed_regions_to_bake_count = 0;
|
|
|
+ processed_regions_to_bake_count_max = 0;
|
|
|
+ region_baking_canceled = false;
|
|
|
+ currently_baking_region = nullptr;
|
|
|
+ bake_in_process = false;
|
|
|
+}
|
|
|
+
|
|
|
+void NavigationRegion3DEditor::_clear_pressed() {
|
|
|
button_bake->set_pressed(false);
|
|
|
bake_info->set_text("");
|
|
|
|
|
|
- if (node) {
|
|
|
- node->update_gizmos();
|
|
|
+ if (!selected_regions.is_empty()) {
|
|
|
+ for (NavigationRegion3D *region : selected_regions) {
|
|
|
+ if (region->get_navigation_mesh().is_valid()) {
|
|
|
+ region->get_navigation_mesh()->clear();
|
|
|
+ region->update_gizmos();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void NavigationRegion3DEditor::edit(NavigationRegion3D *p_nav_region) {
|
|
|
- if (p_nav_region == nullptr || node == p_nav_region) {
|
|
|
+void NavigationRegion3DEditor::edit(LocalVector<NavigationRegion3D *> p_regions) {
|
|
|
+ if (p_regions.is_empty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- node = p_nav_region;
|
|
|
+ selected_regions = p_regions;
|
|
|
}
|
|
|
|
|
|
NavigationRegion3DEditor::NavigationRegion3DEditor() {
|
|
@@ -146,15 +249,56 @@ NavigationRegion3DEditor::NavigationRegion3DEditor() {
|
|
|
|
|
|
err_dialog = memnew(AcceptDialog);
|
|
|
add_child(err_dialog);
|
|
|
- node = nullptr;
|
|
|
+
|
|
|
+ multibake_dialog = memnew(ConfirmationDialog);
|
|
|
+ add_child(multibake_dialog);
|
|
|
+ multibake_dialog->connect(SceneStringName(confirmed), callable_mp(this, &NavigationRegion3DEditor::_on_navmesh_multibake_confirmed));
|
|
|
+ multibake_dialog->connect(SNAME("canceled"), callable_mp(this, &NavigationRegion3DEditor::_on_navmesh_multibake_canceled));
|
|
|
+ multibake_dialog->set_hide_on_ok(false);
|
|
|
+ multibake_dialog->set_title(TTR("Baking NavigationMesh ..."));
|
|
|
}
|
|
|
|
|
|
void NavigationRegion3DEditorPlugin::edit(Object *p_object) {
|
|
|
- navigation_region_editor->edit(Object::cast_to<NavigationRegion3D>(p_object));
|
|
|
+ LocalVector<NavigationRegion3D *> regions;
|
|
|
+
|
|
|
+ {
|
|
|
+ NavigationRegion3D *region = Object::cast_to<NavigationRegion3D>(p_object);
|
|
|
+ if (region) {
|
|
|
+ regions.push_back(region);
|
|
|
+ navigation_region_editor->edit(regions);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Ref<MultiNodeEdit> mne = Ref<MultiNodeEdit>(p_object);
|
|
|
+ Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
|
|
|
+ if (mne.is_valid() && edited_scene) {
|
|
|
+ for (int i = 0; i < mne->get_node_count(); i++) {
|
|
|
+ NavigationRegion3D *region = Object::cast_to<NavigationRegion3D>(edited_scene->get_node(mne->get_node(i)));
|
|
|
+ if (region) {
|
|
|
+ regions.push_back(region);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ navigation_region_editor->edit(regions);
|
|
|
}
|
|
|
|
|
|
bool NavigationRegion3DEditorPlugin::handles(Object *p_object) const {
|
|
|
- return p_object->is_class("NavigationRegion3D");
|
|
|
+ if (Object::cast_to<NavigationRegion3D>(p_object)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ Ref<MultiNodeEdit> mne = Ref<MultiNodeEdit>(p_object);
|
|
|
+ Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
|
|
|
+ if (mne.is_valid() && edited_scene) {
|
|
|
+ for (int i = 0; i < mne->get_node_count(); i++) {
|
|
|
+ if (Object::cast_to<NavigationRegion3D>(edited_scene->get_node(mne->get_node(i)))) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
void NavigationRegion3DEditorPlugin::make_visible(bool p_visible) {
|
|
@@ -164,7 +308,7 @@ void NavigationRegion3DEditorPlugin::make_visible(bool p_visible) {
|
|
|
} else {
|
|
|
navigation_region_editor->hide();
|
|
|
navigation_region_editor->bake_hbox->hide();
|
|
|
- navigation_region_editor->edit(nullptr);
|
|
|
+ navigation_region_editor->edit(LocalVector<NavigationRegion3D *>());
|
|
|
}
|
|
|
}
|
|
|
|