Files
godot/editor/scene/3d/mesh_library_editor_plugin.cpp
T
2026-03-21 11:16:26 -03:00

805 lines
32 KiB
C++

/**************************************************************************/
/* mesh_library_editor_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "mesh_library_editor_plugin.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "editor/docks/editor_dock_manager.h"
#include "editor/docks/inspector_dock.h"
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_zoom_widget.h"
#include "editor/gui/filter_line_edit.h"
#include "editor/inspector/editor_inspector.h"
#include "editor/inspector/editor_resource_preview.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/navigation/navigation_region_3d.h"
#include "scene/3d/physics/static_body_3d.h"
#include "scene/gui/button.h"
#include "scene/gui/item_list.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/separator.h"
#include "scene/gui/split_container.h"
#include "scene/main/timer.h"
#include "scene/resources/3d/mesh_library.h"
#include "scene/resources/packed_scene.h"
bool MeshLibraryEditor::MeshLibraryItem::_set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(mesh_library.is_null(), false);
ERR_FAIL_COND_V(mesh_id == -1, false);
ERR_FAIL_COND_V(!mesh_library->has_item(mesh_id), false);
if (p_name == "name") {
mesh_library->set_item_name(mesh_id, p_value);
} else if (p_name == "mesh") {
mesh_library->set_item_mesh(mesh_id, p_value);
} else if (p_name == "mesh_transform") {
mesh_library->set_item_mesh_transform(mesh_id, p_value);
} else if (p_name == "mesh_cast_shadow") {
switch ((int)p_value) {
case 0: {
mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF);
} break;
case 1: {
mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON);
} break;
case 2: {
mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_DOUBLE_SIDED);
} break;
case 3: {
mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_SHADOWS_ONLY);
} break;
default: {
mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON);
} break;
}
#ifndef PHYSICS_3D_DISABLED
} else if (p_name == "shapes") {
mesh_library->call("set_item_shapes", mesh_id, p_value);
#endif // PHYSICS_3D_DISABLED
} else if (p_name == "preview") {
mesh_library->set_item_preview(mesh_id, p_value);
} else if (p_name == "navigation_mesh") {
mesh_library->set_item_navigation_mesh(mesh_id, p_value);
} else if (p_name == "navigation_mesh_transform") {
mesh_library->set_item_navigation_mesh_transform(mesh_id, p_value);
} else if (p_name == "navigation_layers") {
mesh_library->set_item_navigation_layers(mesh_id, p_value);
} else {
return false;
}
return false;
}
bool MeshLibraryEditor::MeshLibraryItem::_get(const StringName &p_name, Variant &r_ret) const {
ERR_FAIL_COND_V(mesh_library.is_null(), false);
ERR_FAIL_COND_V(mesh_id == -1, false);
ERR_FAIL_COND_V(!mesh_library->has_item(mesh_id), false);
if (p_name == "name") {
r_ret = mesh_library->get_item_name(mesh_id);
} else if (p_name == "mesh") {
r_ret = mesh_library->get_item_mesh(mesh_id);
} else if (p_name == "mesh_transform") {
r_ret = mesh_library->get_item_mesh_transform(mesh_id);
} else if (p_name == "mesh_cast_shadow") {
r_ret = (int)mesh_library->get_item_mesh_cast_shadow(mesh_id);
#ifndef PHYSICS_3D_DISABLED
} else if (p_name == "shapes") {
r_ret = mesh_library->call("get_item_shapes", mesh_id);
#endif // PHYSICS_3D_DISABLED
} else if (p_name == "navigation_mesh") {
r_ret = mesh_library->get_item_navigation_mesh(mesh_id);
} else if (p_name == "navigation_mesh_transform") {
r_ret = mesh_library->get_item_navigation_mesh_transform(mesh_id);
} else if (p_name == "navigation_layers") {
r_ret = mesh_library->get_item_navigation_layers(mesh_id);
} else if (p_name == "preview") {
r_ret = mesh_library->get_item_preview(mesh_id);
} else {
return false;
}
return true;
}
void MeshLibraryEditor::MeshLibraryItem::_get_property_list(List<PropertyInfo> *p_list) const {
if (mesh_id == -1 || mesh_library.is_null() || !mesh_library->has_item(mesh_id)) {
return;
}
p_list->push_back(PropertyInfo(Variant::STRING, PNAME("name")));
p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, Mesh::get_class_static()));
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m"));
p_list->push_back(PropertyInfo(Variant::INT, PNAME("mesh_cast_shadow"), PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"));
p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("shapes")));
p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("navigation_mesh"), PROPERTY_HINT_RESOURCE_TYPE, NavigationMesh::get_class_static()));
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, PNAME("navigation_mesh_transform"), PROPERTY_HINT_NONE, "suffix:m"));
p_list->push_back(PropertyInfo(Variant::INT, PNAME("navigation_layers"), PROPERTY_HINT_LAYERS_3D_NAVIGATION));
p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("preview"), PROPERTY_HINT_RESOURCE_TYPE, Texture2D::get_class_static(), PROPERTY_USAGE_DEFAULT));
}
////////////////
void MeshLibraryEditor::edit(const Ref<MeshLibrary> &p_mesh_library) {
if (mesh_library.is_valid()) {
mesh_library->disconnect_changed(callable_mp(update_items_delay, &Timer::start));
}
selected_item = -1;
mesh_library = p_mesh_library;
if (mesh_library.is_valid()) {
// Avoid updating multiple times at once.
mesh_library->connect_changed(callable_mp(update_items_delay, &Timer::start).bind(UPDATE_ITEMS_DELAY_TIMEOUT));
_update_mesh_items();
}
}
Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge, bool p_apply_xforms) {
_import_scene(p_base_scene, ml, p_merge, p_apply_xforms);
return OK;
}
void MeshLibraryEditor::_update_mesh_items(bool p_reselect, Ref<MeshLibrary> p_lib_check) {
if (p_lib_check.is_valid() && mesh_library != p_lib_check) {
return;
}
mesh_library->set_edited(true);
mesh_items->clear();
remove_item->set_disabled(true);
update_items_delay->stop();
if (mesh_library->get_item_count() == 0) {
_select_item(-1);
item_split->hide();
empty_lib->show();
return;
}
empty_lib->hide();
item_split->show();
float min_size = EDITOR_GET("editors/grid_map/preview_size");
min_size *= EDSCALE;
mesh_items->set_fixed_column_width(min_size * MAX(zoom_widget->get_zoom(), 1.5));
mesh_items->set_fixed_icon_size(Size2(min_size, min_size));
Vector<int> ids;
ids = mesh_library->get_item_list();
struct _CGMEItemSort {
String name;
int id = 0;
_FORCE_INLINE_ bool operator<(const _CGMEItemSort &r_it) const { return name < r_it.name; }
};
List<_CGMEItemSort> il;
for (const int &E : ids) {
_CGMEItemSort is;
is.id = E;
is.name = mesh_library->get_item_name(E);
il.push_back(is);
}
il.sort();
int item = 0;
bool selection_found = false;
String filter = search_box->get_text().strip_edges();
for (_CGMEItemSort &E : il) {
int id = E.id;
String name = mesh_library->get_item_name(id);
if (!filter.is_empty() && !filter.is_subsequence_ofn(name)) {
if (selected_item == id) {
selection_found = true;
}
continue;
}
if (name.is_empty()) {
name = "#" + itos(id);
}
mesh_items->add_item("");
Ref<Texture2D> preview = mesh_library->get_item_preview(id);
if (preview.is_valid()) {
mesh_items->set_item_icon(item, preview);
mesh_items->set_item_tooltip(item, name);
} else {
Ref<Mesh> mesh = mesh_library->get_item_mesh(id);
if (mesh.is_valid()) {
// Fallback to the item's mesh preview.
EditorResourcePreview::get_singleton()->queue_edited_resource_preview(mesh, callable_mp(this, &MeshLibraryEditor::_update_resource_preview).bind(item));
}
}
mesh_items->set_item_text(item, name);
mesh_items->set_item_metadata(item, id);
if (selected_item == id) {
selection_found = true;
if (p_reselect) {
_select_item(id);
mesh_items->select(item);
remove_item->set_disabled(false);
}
}
item++;
}
if (!selection_found) {
if (p_reselect && item > 0) {
_select_item(mesh_items->get_item_metadata(0));
mesh_items->select(0);
remove_item->set_disabled(false);
} else {
_select_item(-1);
}
}
}
void MeshLibraryEditor::_update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, int p_idx) {
if (p_idx < mesh_items->get_item_count()) {
mesh_items->set_item_icon(p_idx, p_preview);
}
}
void MeshLibraryEditor::_select_item(int p_id, Ref<MeshLibrary> p_lib_check) {
if (p_lib_check.is_valid() && mesh_library != p_lib_check) {
return;
}
selected_item = p_id;
if (selected_item == -1) {
inspector->edit(nullptr);
return;
}
Object *edited = inspector->get_edited_object();
if (edited) {
Ref<MeshLibraryItem> item(edited);
if (item->mesh_id == selected_item) {
return; // Already inspecting it.
}
}
mesh_library_item.instantiate();
mesh_library_item->mesh_library = mesh_library;
mesh_library_item->mesh_id = selected_item;
inspector->edit(*mesh_library_item);
}
void MeshLibraryEditor::_select_item_and_button(int p_id, Ref<MeshLibrary> p_lib_check) {
if (p_lib_check.is_valid() && mesh_library != p_lib_check) {
return;
}
_select_item(p_id);
for (int i = 0; i < mesh_items->get_item_count(); i++) {
int id = mesh_items->get_item_metadata(i);
if (id == p_id) {
mesh_items->select(i);
remove_item->set_disabled(false);
return;
}
}
}
void MeshLibraryEditor::_select_prev_item_and_button(int p_id, Ref<MeshLibrary> p_lib_check) {
int idx = -1;
for (int i = 0; i < mesh_items->get_item_count(); i++) {
int id = mesh_items->get_item_metadata(i);
if (id == p_id) {
idx = MAX(i - 1, 0);
break;
}
}
if (idx == -1 && mesh_items->get_item_count() > 0) {
idx = 0;
}
if (idx > -1) {
int id = mesh_items->get_item_metadata(idx);
_select_item(id);
mesh_items->select(idx);
remove_item->set_disabled(false);
}
}
void MeshLibraryEditor::_mesh_items_cbk(int p_idx) {
int id = mesh_items->get_item_metadata(p_idx);
_select_item(id);
remove_item->set_disabled(false);
}
void MeshLibraryEditor::_menu_cbk(int p_option) {
switch (p_option) {
case MENU_OPTION_ADD_ITEM: {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add MeshLibrary item"));
int to_create = mesh_library->get_last_unused_item_id();
undo_redo->add_do_method(*mesh_library, "create_item", to_create);
undo_redo->add_undo_method(*mesh_library, "remove_item", to_create);
undo_redo->add_do_method(this, "_update_mesh_items", false, *mesh_library);
undo_redo->add_undo_method(this, "_update_mesh_items", false, *mesh_library);
undo_redo->add_do_method(this, "_select_item_and_button", to_create, *mesh_library);
if (mesh_library->get_item_count() > 0) {
undo_redo->add_undo_method(this, "_select_prev_item_and_button", to_create, *mesh_library);
}
undo_redo->commit_action();
} break;
case MENU_OPTION_REMOVE_ITEM: {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Remove MeshLibrary item"));
if (mesh_library->get_item_count() - 1 > 0) {
undo_redo->add_do_method(this, "_select_prev_item_and_button", selected_item, *mesh_library);
}
undo_redo->add_do_method(*mesh_library, "remove_item", selected_item);
Ref<Mesh> mesh = mesh_library->get_item_mesh(selected_item);
int mesh_shadow = mesh_library->get_item_mesh_cast_shadow(selected_item);
Transform3D mesh_transform = mesh_library->get_item_mesh_transform(selected_item);
String mesh_name = mesh_library->get_item_name(selected_item);
int nav_layers = mesh_library->get_item_navigation_layers(selected_item);
Ref<NavigationMesh> nav_mesh = mesh_library->get_item_navigation_mesh(selected_item);
Transform3D nav_mesh_transform = mesh_library->get_item_navigation_mesh_transform(selected_item);
Ref<Texture2D> preview = mesh_library->get_item_preview(selected_item);
Array shapes = mesh_library->call("get_item_shapes", selected_item);
undo_redo->add_undo_method(*mesh_library, "create_item", selected_item);
undo_redo->add_undo_method(*mesh_library, "set_item_mesh", selected_item, mesh);
undo_redo->add_undo_method(*mesh_library, "set_item_mesh_cast_shadow", selected_item, mesh_shadow);
undo_redo->add_undo_method(*mesh_library, "set_item_mesh_transform", selected_item, mesh_transform);
undo_redo->add_undo_method(*mesh_library, "set_item_name", selected_item, mesh_name);
undo_redo->add_undo_method(*mesh_library, "set_item_navigation_layers", selected_item, nav_layers);
undo_redo->add_undo_method(*mesh_library, "set_item_navigation_mesh", selected_item, nav_mesh);
undo_redo->add_undo_method(*mesh_library, "set_item_navigation_mesh_transform", selected_item, nav_mesh_transform);
undo_redo->add_undo_method(*mesh_library, "set_item_preview", selected_item, preview);
undo_redo->add_undo_method(*mesh_library, "set_item_shapes", selected_item, shapes);
undo_redo->add_do_method(this, "_update_mesh_items", true, *mesh_library);
undo_redo->add_undo_method(this, "_update_mesh_items", false, *mesh_library);
undo_redo->add_undo_method(this, "_select_item_and_button", selected_item, *mesh_library);
undo_redo->commit_action();
} break;
case MENU_OPTION_IMPORT_FROM_SCENE: {
apply_xforms = false;
import_update = false;
file->popup_file_dialog();
} break;
case MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS: {
apply_xforms = true;
import_update = false;
file->popup_file_dialog();
} break;
case MENU_OPTION_UPDATE_FROM_SCENE: {
import_update = true;
cd_update->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene"))));
cd_update->popup_centered(Size2(500, 60));
} break;
}
}
void MeshLibraryEditor::_menu_update_confirm(bool p_apply_xforms) {
cd_update->hide();
apply_xforms = p_apply_xforms;
String existing = mesh_library->get_meta("_editor_source_scene");
ERR_FAIL_COND(existing.is_empty());
_import_scene_cbk(existing);
}
void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms) {
if (!p_merge) {
p_library->clear();
}
HashMap<int, MeshInstance3D *> mesh_instances;
for (int i = 0; i < p_scene->get_child_count(); i++) {
_import_scene_parse_node(p_library, mesh_instances, p_scene->get_child(i), p_merge, p_apply_xforms);
}
// Generate previews.
Vector<Ref<Mesh>> meshes;
Vector<Transform3D> transforms;
Vector<int> ids = p_library->get_item_list();
for (const int &E : ids) {
if (mesh_instances.find(E)) {
meshes.push_back(p_library->get_item_mesh(E));
transforms.push_back(mesh_instances[E]->get_transform());
}
}
Vector<Ref<Texture2D>> textures = EditorInterface::get_singleton()->make_mesh_previews(meshes, &transforms, EDITOR_GET("editors/grid_map/preview_size"));
int idx = 0;
for (const int &E : ids) {
if (mesh_instances.find(E)) {
p_library->set_item_preview(E, textures[idx]);
idx++;
}
}
}
void MeshLibraryEditor::_import_scene_cbk(const String &p_str) {
Ref<PackedScene> ps = ResourceLoader::load(p_str, "PackedScene");
ERR_FAIL_COND(ps.is_null());
Node *scene = ps->instantiate();
ERR_FAIL_NULL_MSG(scene, "Cannot create an instance from PackedScene '" + p_str + "'.");
// Preserve the data from the current library.
Ref<MeshLibrary> old_lib;
old_lib = mesh_library->duplicate();
_import_scene(scene, mesh_library, import_update, apply_xforms);
memdelete(scene);
mesh_library->set_meta("_editor_source_scene", p_str);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Import MeshLibrary from scene"));
undo_redo->add_do_method(*mesh_library, "copy_from_resource", *mesh_library->duplicate());
undo_redo->add_undo_method(*mesh_library, "copy_from_resource", old_lib);
undo_redo->commit_action(false);
import_scene->get_popup()->set_item_disabled(import_scene->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), false);
}
void MeshLibraryEditor::_import_scene_parse_node(Ref<MeshLibrary> p_library, HashMap<int, MeshInstance3D *> &p_mesh_instances, Node *p_node, bool p_merge, bool p_apply_xforms) {
MeshInstance3D *mesh_instance_node = Object::cast_to<MeshInstance3D>(p_node);
if (!mesh_instance_node) {
// No MeshInstance so search deeper.
for (int i = 0; i < p_node->get_child_count(); i++) {
_import_scene_parse_node(p_library, p_mesh_instances, p_node->get_child(i), p_merge, p_apply_xforms);
}
return;
}
Ref<Mesh> source_mesh = mesh_instance_node->get_mesh();
if (source_mesh.is_null()) {
return;
}
int item_id = p_library->find_item_by_name(mesh_instance_node->get_name());
if (item_id < 0) {
item_id = p_library->get_last_unused_item_id();
p_library->create_item(item_id);
p_library->set_item_name(item_id, mesh_instance_node->get_name());
} else if (!p_merge) {
WARN_PRINT(vformat("MeshLibrary export found a MeshInstance3D with a duplicated name '%s' in the exported scene that overrides a previously parsed MeshInstance3D item with the same name.", mesh_instance_node->get_name()));
}
p_mesh_instances[item_id] = mesh_instance_node;
Ref<Mesh> item_mesh = source_mesh->duplicate();
for (int i = 0; i < item_mesh->get_surface_count(); i++) {
Ref<Material> surface_override_material = mesh_instance_node->get_surface_override_material(i);
if (surface_override_material.is_valid()) {
item_mesh->surface_set_material(i, surface_override_material);
}
}
p_library->set_item_mesh(item_id, item_mesh);
GeometryInstance3D::ShadowCastingSetting gi3d_cast_shadows_setting = mesh_instance_node->get_cast_shadows_setting();
switch (gi3d_cast_shadows_setting) {
case GeometryInstance3D::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF: {
p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF);
} break;
case GeometryInstance3D::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON: {
p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON);
} break;
case GeometryInstance3D::ShadowCastingSetting::SHADOW_CASTING_SETTING_DOUBLE_SIDED: {
p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_DOUBLE_SIDED);
} break;
case GeometryInstance3D::ShadowCastingSetting::SHADOW_CASTING_SETTING_SHADOWS_ONLY: {
p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_SHADOWS_ONLY);
} break;
default: {
p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON);
} break;
}
Transform3D item_mesh_transform;
if (p_apply_xforms) {
item_mesh_transform = mesh_instance_node->get_transform();
}
p_library->set_item_mesh_transform(item_id, item_mesh_transform);
Vector<MeshLibrary::ShapeData> collisions;
for (int i = 0; i < mesh_instance_node->get_child_count(); i++) {
StaticBody3D *static_body_node = Object::cast_to<StaticBody3D>(mesh_instance_node->get_child(i));
if (!static_body_node) {
continue;
}
List<uint32_t> shapes;
static_body_node->get_shape_owners(&shapes);
for (uint32_t &E : shapes) {
if (static_body_node->is_shape_owner_disabled(E)) {
continue;
}
Transform3D shape_transform;
if (p_apply_xforms) {
shape_transform = mesh_instance_node->get_transform();
}
shape_transform *= static_body_node->get_transform() * static_body_node->shape_owner_get_transform(E);
for (int k = 0; k < static_body_node->shape_owner_get_shape_count(E); k++) {
Ref<Shape3D> collision_shape = static_body_node->shape_owner_get_shape(E, k);
if (collision_shape.is_null()) {
continue;
}
MeshLibrary::ShapeData shape_data;
shape_data.shape = collision_shape;
shape_data.local_transform = shape_transform;
collisions.push_back(shape_data);
}
}
}
p_library->set_item_shapes(item_id, collisions);
for (int i = 0; i < mesh_instance_node->get_child_count(); i++) {
NavigationRegion3D *navigation_region_node = Object::cast_to<NavigationRegion3D>(mesh_instance_node->get_child(i));
if (!navigation_region_node) {
continue;
}
Ref<NavigationMesh> navigation_mesh = navigation_region_node->get_navigation_mesh();
if (navigation_mesh.is_valid()) {
Transform3D navigation_mesh_transform = navigation_region_node->get_transform();
p_library->set_item_navigation_mesh(item_id, navigation_mesh);
p_library->set_item_navigation_mesh_transform(item_id, navigation_mesh_transform);
break;
}
}
}
void MeshLibraryEditor::_icon_size_changed(float p_value) {
mesh_items->set_icon_scale(p_value);
_update_mesh_items();
}
void MeshLibraryEditor::_mesh_items_input(const Ref<InputEvent> &p_event) {
// Zoom in/out using Ctrl + mouse wheel.
const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->is_pressed() && mb->is_command_or_control_pressed()) {
if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) {
zoom_widget->set_zoom(zoom_widget->get_zoom() + 0.2);
zoom_widget->emit_signal(SNAME("zoom_changed"), zoom_widget->get_zoom());
accept_event();
}
if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) {
zoom_widget->set_zoom(zoom_widget->get_zoom() - 0.2);
zoom_widget->emit_signal(SNAME("zoom_changed"), zoom_widget->get_zoom());
accept_event();
}
}
}
void MeshLibraryEditor::_notification(int p_what) {
if (p_what == NOTIFICATION_THEME_CHANGED) {
add_item->set_button_icon(get_editor_theme_icon(SNAME("Add")));
remove_item->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
}
}
void MeshLibraryEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_mesh_items", "reselect"), &MeshLibraryEditor::_update_mesh_items, DEFVAL(true));
ClassDB::bind_method(D_METHOD("_select_item_and_button", "id", "library"), &MeshLibraryEditor::_select_item_and_button, DEFVAL(Ref<MeshLibrary>()));
ClassDB::bind_method(D_METHOD("_select_prev_item_and_button", "id", "library"), &MeshLibraryEditor::_select_prev_item_and_button, DEFVAL(Ref<MeshLibrary>()));
}
MeshLibraryEditor::MeshLibraryEditor() {
set_name(TTRC("MeshLibrary"));
set_icon_name("MeshLibraryEditor");
set_default_slot(EditorDock::DOCK_SLOT_BOTTOM);
set_available_layouts(EditorDock::DOCK_LAYOUT_HORIZONTAL | EditorDock::DOCK_LAYOUT_FLOATING);
set_global(false);
set_transient(true);
set_custom_minimum_size(Size2(0, 200 * EDSCALE));
VBoxContainer *vb = memnew(VBoxContainer);
add_child(vb);
HBoxContainer *toolbar = memnew(HBoxContainer);
vb->add_child(toolbar);
add_item = memnew(Button);
add_item->set_text(TTRC("Add"));
add_item->set_theme_type_variation(SceneStringName(FlatButton));
add_item->connect(SceneStringName(pressed), callable_mp(this, &MeshLibraryEditor::_menu_cbk).bind(MENU_OPTION_ADD_ITEM));
toolbar->add_child(add_item);
remove_item = memnew(Button);
remove_item->set_text(TTRC("Remove"));
remove_item->set_disabled(true);
remove_item->set_theme_type_variation(SceneStringName(FlatButton));
remove_item->connect(SceneStringName(pressed), callable_mp(this, &MeshLibraryEditor::_menu_cbk).bind(MENU_OPTION_REMOVE_ITEM));
toolbar->add_child(remove_item);
toolbar->add_child(memnew(VSeparator));
import_scene = memnew(MenuButton);
import_scene->set_text(TTRC("Import"));
import_scene->set_flat(false);
import_scene->set_theme_type_variation(SceneStringName(FlatButton));
toolbar->add_child(import_scene);
import_scene->get_popup()->add_item(TTRC("Import from Scene (Ignore Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE);
import_scene->get_popup()->add_item(TTRC("Import from Scene (Apply Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS);
import_scene->get_popup()->add_separator();
import_scene->get_popup()->add_item(TTRC("Update from Scene"), MENU_OPTION_UPDATE_FROM_SCENE);
import_scene->get_popup()->set_item_disabled(-1, true);
import_scene->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &MeshLibraryEditor::_menu_cbk));
toolbar->add_spacer();
update_items_delay = memnew(Timer);
update_items_delay->set_wait_time(UPDATE_ITEMS_DELAY_TIMEOUT);
update_items_delay->set_one_shot(true);
update_items_delay->connect("timeout", callable_mp(this, &MeshLibraryEditor::_update_mesh_items).bind(true, Ref<MeshLibrary>()));
add_child(update_items_delay);
search_box = memnew(FilterLineEdit);
search_box->set_placeholder(TTRC("Filter Meshes"));
search_box->set_accessibility_name(TTRC("Filter Meshes"));
search_box->add_theme_constant_override("minimum_character_width", 10);
search_box->set_h_size_flags(SIZE_EXPAND_FILL);
toolbar->add_child(search_box);
search_box->connect(SceneStringName(text_changed), callable_mp(update_items_delay, &Timer::start).bind(UPDATE_ITEMS_DELAY_TIMEOUT).unbind(1));
zoom_widget = memnew(EditorZoomWidget);
zoom_widget->setup_zoom_limits(0.2, 4);
zoom_widget->connect("zoom_changed", callable_mp(this, &MeshLibraryEditor::_icon_size_changed));
zoom_widget->set_shortcut_context(this);
toolbar->add_child(zoom_widget);
item_split = memnew(HSplitContainer);
item_split->set_v_size_flags(SIZE_EXPAND_FILL);
vb->add_child(item_split);
item_split->hide();
mesh_items = memnew(ItemList);
mesh_items->set_max_columns(0);
mesh_items->set_icon_mode(ItemList::ICON_MODE_TOP);
mesh_items->set_max_text_lines(2);
mesh_items->set_theme_type_variation("ItemListSecondary");
mesh_items->set_h_size_flags(SIZE_EXPAND_FILL);
mesh_items->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
mesh_items->set_auto_translate(false);
item_split->add_child(mesh_items);
mesh_items->connect(SceneStringName(item_selected), callable_mp(this, &MeshLibraryEditor::_mesh_items_cbk));
mesh_items->connect(SceneStringName(gui_input), callable_mp(this, &MeshLibraryEditor::_mesh_items_input));
search_box->set_forward_control(mesh_items);
inspector = memnew(EditorInspector);
inspector->set_use_doc_hints(true);
inspector->set_theme_type_variation("ScrollContainerSecondary");
inspector->set_h_size_flags(SIZE_EXPAND_FILL);
inspector->set_stretch_ratio(0.3);
inspector->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
item_split->add_child(inspector);
inspector->add_custom_property_description("MeshLibraryItem", "name", TTRC("The item's name, shown in the editor. It can also be used to look up the item later using [method MeshLibrary.find_item_by_name]."));
inspector->add_custom_property_description("MeshLibraryItem", "mesh", TTRC("The item's mesh. Used by other parts of the engine (e.g. [GridMap], which displays them in a 3D tile)."));
inspector->add_custom_property_description("MeshLibraryItem", "mesh_transform", TTRC("The transform to apply to the item's mesh."));
inspector->add_custom_property_description("MeshLibraryItem", "mesh_cast_shadow", TTRC("The shadow casting mode used by the item's mesh. See [enum RenderingServer.ShadowCastingSetting]"));
inspector->add_custom_property_description("MeshLibraryItem", "shapes", TTRC("The item's collision shapes.\nThe array should consist of [Shape3D] objects, each followed by a [Transform3D] that will be applied to it. For shapes that should not have a transform, use [constant Transform3D.IDENTITY]."));
inspector->add_custom_property_description("MeshLibraryItem", "navigation_mesh", TTRC("The item's navigation mesh."));
inspector->add_custom_property_description("MeshLibraryItem", "navigation_mesh_transform", TTRC("The transform to apply to the item's navigation mesh."));
inspector->add_custom_property_description("MeshLibraryItem", "navigation_layers", TTRC("The item's navigation layers bitmask."));
inspector->add_custom_property_description("MeshLibraryItem", "preview", TTRC("The texture to use as the item's preview icon in the editor."));
empty_lib = memnew(Label);
empty_lib->set_text(TTRC("No items found inside the MeshLibrary.\nYou can add some by using the Add button on the left, or by exporting them from a scene file via the Export menu."));
empty_lib->set_focus_mode(FOCUS_ACCESSIBILITY);
empty_lib->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
empty_lib->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
empty_lib->set_v_size_flags(SIZE_EXPAND_FILL);
empty_lib->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
empty_lib->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
vb->add_child(empty_lib);
empty_lib->hide();
file = memnew(EditorFileDialog);
file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
List<String> extensions;
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions);
file->clear_filters();
file->set_title(TTRC("Import Scene"));
for (const String &extension : extensions) {
file->add_filter("*." + extension, extension.to_upper());
}
add_child(file);
file->connect("file_selected", callable_mp(this, &MeshLibraryEditor::_import_scene_cbk));
cd_update = memnew(ConfirmationDialog);
add_child(cd_update);
cd_update->set_ok_button_text(TTRC("Apply without Transforms"));
cd_update->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &MeshLibraryEditor::_menu_update_confirm).bind(false));
cd_update->add_button(TTRC("Apply with Transforms"))->connect(SceneStringName(pressed), callable_mp(this, &MeshLibraryEditor::_menu_update_confirm).bind(true));
}
////////////////
void MeshLibraryEditorPlugin::edit(Object *p_node) {
Ref<MeshLibrary> ml = Object::cast_to<MeshLibrary>(p_node);
if (ml.is_valid()) {
mesh_library_editor->edit(ml);
}
}
bool MeshLibraryEditorPlugin::handles(Object *p_object) const {
return p_object && p_object->is_class("MeshLibrary");
}
void MeshLibraryEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
mesh_library_editor->make_visible();
} else {
mesh_library_editor->close();
}
}
MeshLibraryEditorPlugin::MeshLibraryEditorPlugin() {
singleton = this;
mesh_library_editor = memnew(MeshLibraryEditor);
EditorDockManager::get_singleton()->add_dock(mesh_library_editor);
mesh_library_editor->close();
}