diff --git a/doc/classes/ResourceImporterScene.xml b/doc/classes/ResourceImporterScene.xml index 517df87173..44b0bca16a 100644 --- a/doc/classes/ResourceImporterScene.xml +++ b/doc/classes/ResourceImporterScene.xml @@ -30,6 +30,9 @@ If [code]true[/code], trim the beginning and end of animations if there are no keyframe changes. This can reduce output file size and memory usage with certain 3D scenes, depending on the contents of their animation tracks. + + If the 3D model file contains only one mesh, this option has no effect. If [code]true[/code] and the 3D model file contains multiple meshes with the same surface names and formats, the surfaces will be merged together when the meshes are merged. This is useful for reducing the number of surfaces in the resulting mesh, and avoids duplicating materials. If [code]false[/code] and the 3D model file contains multiple meshes, the surfaces will always be kept separate. + Path to an import script, which can run code after the import process has completed for custom processing. See [url=$DOCS_URL/tutorials/assets_pipeline/importing_3d_scenes/import_configuration.html#using-import-scripts-for-automation]Using import scripts for automation[/url] for more information. @@ -48,6 +51,9 @@ Path extracted materials are saved to. If empty, source scene path is used. + + If [code]true[/code], the mesh names will be set to the names of the nodes in the 3D model file. If [code]false[/code], the mesh names will be set to the names of the meshes in the 3D model file. Enabling this is a common work-around when the author of the 3D model file did not properly set the mesh names in Blender or other 3D modeling apps. For example, a file may have a node named "Turret" with a mesh named "Cube.002", so enabling this option will set the mesh name to "Turret" instead of "Cube_002". + If [code]true[/code], enables the generation of shadow meshes on import. This optimizes shadow rendering without reducing quality by welding vertices together when possible. This in turn reduces the memory bandwidth required to render shadows. Shadow mesh generation currently doesn't support using a lower detail level than the source mesh (but shadow rendering will make use of LODs when relevant). diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index cb7b8a33a2..9666732d44 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -8533,6 +8533,12 @@ EditorNode::EditorNode() { import_model_as_animation.instantiate("AnimationLibrary"); ResourceFormatImporter::get_singleton()->add_importer(import_model_as_animation); + Ref import_scene_as_mesh_library = memnew(ResourceImporterScene("MeshLibrary")); + ResourceFormatImporter::get_singleton()->add_importer(import_scene_as_mesh_library); + + Ref import_scene_as_single_mesh = memnew(ResourceImporterScene("ArrayMesh")); + ResourceFormatImporter::get_singleton()->add_importer(import_scene_as_single_mesh); + { Ref import_collada; import_collada.instantiate(); diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 17e8817e7a..f318308031 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -51,6 +51,7 @@ #include "scene/animation/animation_player.h" #include "scene/resources/3d/box_shape_3d.h" #include "scene/resources/3d/importer_mesh.h" +#include "scene/resources/3d/mesh_library.h" #include "scene/resources/3d/separation_ray_shape_3d.h" #include "scene/resources/3d/sphere_shape_3d.h" #include "scene/resources/3d/world_boundary_shape_3d.h" @@ -262,6 +263,8 @@ String ResourceImporterScene::get_visible_name() const { // This is displayed on the UI. Friendly names here are nice but not vital, so fall back to the type. if (_scene_import_type == "PackedScene") { return "Scene"; + } else if (_scene_import_type == "ArrayMesh") { + return "Single Mesh"; } return _scene_import_type.capitalize(); } @@ -286,6 +289,12 @@ int ResourceImporterScene::get_format_version() const { } bool ResourceImporterScene::get_option_visibility(const String &p_path, const String &p_option, const HashMap &p_options) const { + if (p_option.begins_with("mesh_library/")) { + return _scene_import_type == "MeshLibrary"; + } + if (p_option.begins_with("array_mesh/")) { + return _scene_import_type == "ArrayMesh"; + } if (_scene_import_type == "PackedScene") { if (p_option.begins_with("animation/")) { if (p_option != "animation/import" && !bool(p_options["animation/import"])) { @@ -296,9 +305,23 @@ bool ResourceImporterScene::get_option_visibility(const String &p_path, const St if (p_option == "animation/import") { // Option ignored, animation always imported. return false; } - if (p_option == "nodes/root_type" || p_option == "nodes/root_name" || p_option.begins_with("meshes/") || p_option.begins_with("skins/")) { + if (p_option == "nodes/root_type" || p_option == "nodes/root_name" || p_option == "nodes/root_script" || p_option.begins_with("meshes/") || p_option.begins_with("skins/")) { return false; // Nothing to do here for animations. } + } else if (_scene_import_type == "MeshLibrary") { + if (p_option.begins_with("animation/") || p_option.begins_with("skins/") || p_option.begins_with("import_script/")) { + return false; + } + if (p_option.begins_with("nodes/")) { + return p_option == "nodes/root_scale"; + } + } else if (_scene_import_type == "ArrayMesh") { + if (p_option.begins_with("animation/") || p_option.begins_with("skins/") || p_option.begins_with("import_script/")) { + return false; + } + if (p_option.begins_with("nodes/")) { + return p_option == "nodes/root_scale"; + } } if (p_option == "nodes/use_node_type_suffixes" && p_options.has("nodes/use_name_suffixes")) { @@ -2118,6 +2141,73 @@ void ResourceImporterScene::_compress_animations(AnimationPlayer *anim, int p_pa } } +Error ResourceImporterScene::_save_scene_as_mesh_library(const String &p_source_file, const String &p_save_path, Node *p_godot_scene, const HashMap &p_options, int p_flags) { + TypedArray mesh_instances = p_godot_scene->find_children("*", "MeshInstance3D", true, false); + const int mesh_inst_count = mesh_instances.size(); + HashSet> unique_meshes; + const bool use_node_names_as_mesh_names = p_options.has("mesh_library/use_node_names_as_mesh_names") && p_options["mesh_library/use_node_names_as_mesh_names"]; + for (int mesh_inst_i = 0; mesh_inst_i < mesh_inst_count; mesh_inst_i++) { + MeshInstance3D *mesh_inst = Object::cast_to(mesh_instances[mesh_inst_i]); + Ref mesh = mesh_inst->get_mesh(); + if (mesh.is_valid()) { + if (unique_meshes.has(mesh)) { + continue; + } + if (use_node_names_as_mesh_names) { + mesh->set_name(mesh_inst->get_name()); + } + unique_meshes.insert(mesh); + } + } + Ref mesh_library; + mesh_library.instantiate(); + for (Ref mesh : unique_meshes) { + // The scene importers guarantee mesh names to be unique and non-empty, so we can use it safely without fallback. + const String mesh_name = mesh->get_name(); + const int id = mesh_library->get_last_unused_item_id(); + mesh_library->create_item(id); + mesh_library->set_item_name(id, mesh_name); + mesh_library->set_item_mesh(id, mesh); + } + return ResourceSaver::save(mesh_library, p_save_path + ".res", p_flags); +} + +Error ResourceImporterScene::_save_scene_as_single_mesh(const String &p_source_file, const String &p_save_path, Node *p_godot_scene, const HashMap &p_options, int p_flags) { + TypedArray mesh_instance_nodes = p_godot_scene->find_children("*", "MeshInstance3D", true, false); + const int mesh_inst_count = mesh_instance_nodes.size(); + ERR_FAIL_COND_V_MSG(mesh_inst_count == 0, ERR_INVALID_DATA, "Cannot import GLTF file " + p_source_file + " as a single mesh, because it contains no meshes."); + const String save_file_path = p_save_path + ".res"; + // If there is just one mesh, we can save it directly, preserving that one mesh exactly as it is. + if (mesh_inst_count == 1) { + MeshInstance3D *mesh_inst_node = Object::cast_to(mesh_instance_nodes[0]); + Ref array_mesh = mesh_inst_node->get_mesh(); + if (array_mesh.is_valid()) { + array_mesh->set_path(save_file_path, true); + return ResourceSaver::save(array_mesh, save_file_path, p_flags); + } + } + // If the file contains multiple meshes, we have to merge them into a single mesh, based on their positions in the scene. + TypedArray importer_mesh_instances; + TypedArray relative_transforms; + for (int i = 0; i < mesh_inst_count; i++) { + MeshInstance3D *mesh_inst_node = Object::cast_to(mesh_instance_nodes[i]); + Ref array_mesh = mesh_inst_node->get_mesh(); + if (array_mesh.is_valid()) { + Transform3D global_transform = mesh_inst_node->get_transform(); + Node3D *parent = Object::cast_to(mesh_inst_node->get_parent()); + while (parent != nullptr) { + global_transform = parent->get_transform() * global_transform; + parent = Object::cast_to(parent->get_parent()); + } + importer_mesh_instances.append(ImporterMesh::from_mesh(array_mesh)); + relative_transforms.append(global_transform); + } + } + const bool deduplicate_surfaces = p_options.has("array_mesh/deduplicate_surfaces") && p_options["array_mesh/deduplicate_surfaces"]; + Ref merged_mesh = ImporterMesh::merge_importer_meshes(importer_mesh_instances, relative_transforms, deduplicate_surfaces); + return ResourceSaver::save(merged_mesh->get_mesh(), save_file_path, p_flags); +} + void ResourceImporterScene::get_internal_import_options(InternalImportCategory p_category, List *r_options) const { switch (p_category) { case INTERNAL_IMPORT_CATEGORY_NODE: { @@ -2520,6 +2610,8 @@ void ResourceImporterScene::get_import_options(const String &p_path, Listpush_back(ImportOption(PropertyInfo(Variant::BOOL, "mesh_library/use_node_names_as_mesh_names"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "array_mesh/deduplicate_surfaces"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/apply_root_scale"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "nodes/root_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001"), 1.0)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/import_as_skeleton_bones"), false)); @@ -3159,6 +3251,9 @@ Error ResourceImporterScene::import(ResourceUID::ID p_source_id, const String &p bool apply_root = true; if (p_options.has("nodes/apply_root_scale")) { apply_root = p_options["nodes/apply_root_scale"]; + if (_scene_import_type == "ArrayMesh" || _scene_import_type == "MeshLibrary") { + apply_root = true; + } } real_t root_scale = 1; if (p_options.has("nodes/root_scale")) { @@ -3359,6 +3454,10 @@ Error ResourceImporterScene::import(ResourceUID::ID p_source_id, const String &p err = ResourceSaver::save(packer, p_save_path + ".scn", flags); //do not take over, let the changed files reload themselves ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save scene to file '" + p_save_path + ".scn'."); EditorInterface::get_singleton()->make_scene_preview(p_source_file, scene, 1024); + } else if (_scene_import_type == "ArrayMesh") { + _save_scene_as_single_mesh(p_source_file, p_save_path, scene, p_options, flags); + } else if (_scene_import_type == "MeshLibrary") { + _save_scene_as_mesh_library(p_source_file, p_save_path, scene, p_options, flags); } else { ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown scene import type: " + _scene_import_type); } diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h index abb4e403bc..515aff7b60 100644 --- a/editor/import/3d/resource_importer_scene.h +++ b/editor/import/3d/resource_importer_scene.h @@ -289,6 +289,9 @@ public: void _optimize_animations(AnimationPlayer *anim, float p_max_vel_error, float p_max_ang_error, int p_prc_error); void _compress_animations(AnimationPlayer *anim, int p_page_size_kb); + Error _save_scene_as_mesh_library(const String &p_source_file, const String &p_save_path, Node *p_godot_scene, const HashMap &p_options, int p_flags); + Error _save_scene_as_single_mesh(const String &p_source_file, const String &p_save_path, Node *p_godot_scene, const HashMap &p_options, int p_flags); + Node *pre_import(const String &p_source_file, const HashMap &p_options); virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 94c0d7c573..624d41d5ad 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -753,11 +753,11 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, const String data_mode->set_current_tab(0); } - // Only show the save data options for PackedScene imports of scenes, not resource imports. - const bool disable_save_mesh_mat = p_scene_import_type != "PackedScene"; + // Only show the save mesh options for PackedScene and MeshLibrary imports of scenes. + const bool disable_save_mesh_mat = p_scene_import_type != "PackedScene" && p_scene_import_type != "MeshLibrary"; action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_EXTRACT_MATERIALS), disable_save_mesh_mat); action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_CHOOSE_MESH_SAVE_PATHS), disable_save_mesh_mat); - const bool disable_save_anim = disable_save_mesh_mat && p_scene_import_type != "AnimationLibrary"; + const bool disable_save_anim = p_scene_import_type != "PackedScene" && p_scene_import_type != "AnimationLibrary"; action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_CHOOSE_ANIMATION_SAVE_PATHS), disable_save_anim); base_path = p_path;