diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 853e3e813b1b..ae38eba22550 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -2617,6 +2617,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_ mesh_node->set_layer_mask(src_mesh_node->get_layer_mask()); mesh_node->set_cast_shadows_setting(src_mesh_node->get_cast_shadows_setting()); + mesh_node->set_visible(src_mesh_node->is_visible()); mesh_node->set_visibility_range_begin(src_mesh_node->get_visibility_range_begin()); mesh_node->set_visibility_range_begin_margin(src_mesh_node->get_visibility_range_begin_margin()); mesh_node->set_visibility_range_end(src_mesh_node->get_visibility_range_end()); diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 7ca3cb6c3a43..e309081ad9f1 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -367,6 +367,7 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite mesh_node->set_transform(src_mesh_node->get_transform()); mesh_node->set_skin(src_mesh_node->get_skin()); mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path()); + mesh_node->set_visible(src_mesh_node->is_visible()); if (src_mesh_node->get_mesh().is_valid()) { Ref editor_mesh = src_mesh_node->get_mesh(); mesh_node->set_mesh(editor_mesh->get_mesh()); diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 10534594d36d..a83ae75df6bb 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -108,6 +108,9 @@ How to process the root node during export. See [enum RootNodeMode] for details. The default and recommended value is [constant ROOT_NODE_MODE_SINGLE_ROOT]. [b]Note:[/b] Regardless of how the glTF file is exported, when importing, the root node type and name can be overridden in the scene import settings tab. + + How to deal with node visibility during export. This setting does nothing if all nodes are visible. See [enum VisibilityMode] for details. The default and recommended value is [constant VISIBILITY_MODE_INCLUDE_REQUIRED], which uses the [code]KHR_node_visibility[/code] extension. + @@ -119,5 +122,14 @@ Treat the Godot scene's root node as the name of the glTF scene, and add all of its children as root nodes of the glTF file. This uses only vanilla glTF features. This avoids an extra root node, but only the name of the Godot scene's root node will be preserved, as it will not be saved as a node. + + If the scene contains any non-visible nodes, include them, mark them as non-visible with [code]KHR_node_visibility[/code], and require that importers respect their non-visibility. Downside: If the importer does not support [code]KHR_node_visibility[/code], the file cannot be imported. + + + If the scene contains any non-visible nodes, include them, mark them as non-visible with [code]KHR_node_visibility[/code], and do not impose any requirements on importers. Downside: If the importer does not support [code]KHR_node_visibility[/code], invisible objects will be visible. + + + If the scene contains any non-visible nodes, do not include them in the export. This is the same as the behavior in Godot 4.3 and earlier. Downside: Invisible nodes will not exist in the exported file. + diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index 2786c25e9adc..3fb9a9123a0e 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -67,6 +67,9 @@ If this glTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin. + + If [code]true[/code], the GLTF node is visible. If [code]false[/code], the GLTF node is not visible. This is translated to the [member Node3D.visible] property in the Godot scene, and is exported to [code]KHR_node_visibility[/code] when [code]false[/code]. + The transform of the glTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred. diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp index 022d2e44771b..872054ec2e58 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp @@ -88,7 +88,7 @@ void SceneExporterGLTFPlugin::_popup_gltf_export_dialog() { } _file_dialog->set_current_file(filename + String(".gltf")); // Generate and refresh the export settings. - _export_settings->generate_property_list(_gltf_document); + _export_settings->generate_property_list(_gltf_document, root); _settings_inspector->edit(nullptr); _settings_inspector->edit(_export_settings.ptr()); // Show the file dialog. diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp index 511da078d86f..f5c3eaa2f2ea 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp @@ -50,6 +50,10 @@ bool EditorSceneExporterGLTFSettings::_set(const StringName &p_name, const Varia _document->set_root_node_mode((GLTFDocument::RootNodeMode)(int64_t)p_value); return true; } + if (p_name == StringName("visibility_mode")) { + _document->set_visibility_mode((GLTFDocument::VisibilityMode)(int64_t)p_value); + return true; + } return false; } @@ -70,6 +74,10 @@ bool EditorSceneExporterGLTFSettings::_get(const StringName &p_name, Variant &r_ r_ret = _document->get_root_node_mode(); return true; } + if (p_name == StringName("visibility_mode")) { + r_ret = _document->get_visibility_mode(); + return true; + } return false; } @@ -128,8 +136,23 @@ String get_friendly_config_prefix(Ref p_extension) { return "Unknown GLTFDocumentExtension"; } +bool is_any_node_invisible(Node *p_node) { + if (p_node->has_method("is_visible")) { + bool visible = p_node->call("is_visible"); + if (!visible) { + return true; + } + } + for (int i = 0; i < p_node->get_child_count(); i++) { + if (is_any_node_invisible(p_node->get_child(i))) { + return true; + } + } + return false; +} + // Run this before popping up the export settings, because the extensions may have changed. -void EditorSceneExporterGLTFSettings::generate_property_list(Ref p_document) { +void EditorSceneExporterGLTFSettings::generate_property_list(Ref p_document, Node *p_root) { _property_list.clear(); _document = p_document; String image_format_hint_string = "None,PNG,JPEG"; @@ -168,6 +191,11 @@ void EditorSceneExporterGLTFSettings::generate_property_list(Ref p _property_list.push_back(lossy_quality_prop); PropertyInfo root_node_mode_prop = PropertyInfo(Variant::INT, "root_node_mode", PROPERTY_HINT_ENUM, "Single Root,Keep Root,Multi Root"); _property_list.push_back(root_node_mode_prop); + // If the scene contains any non-visible nodes, show the visibility mode setting. + if (p_root != nullptr && is_any_node_invisible(p_root)) { + PropertyInfo visibility_mode_prop = PropertyInfo(Variant::INT, "visibility_mode", PROPERTY_HINT_ENUM, "Include & Required,Include & Optional,Exclude"); + _property_list.push_back(visibility_mode_prop); + } } String EditorSceneExporterGLTFSettings::get_copyright() const { diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h index 898cddfd6890..aa0e54078d7c 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h +++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h @@ -55,7 +55,7 @@ class EditorSceneExporterGLTFSettings : public RefCounted { bool _get_extension_setting(const String &p_name_str, Variant &r_ret) const; public: - void generate_property_list(Ref p_document); + void generate_property_list(Ref p_document, Node *p_root = nullptr); String get_copyright() const; void set_copyright(const String &p_copyright); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 77bba940cb42..f17ee6fea4d2 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -412,6 +412,17 @@ Error GLTFDocument::_serialize_nodes(Ref p_state) { extensions["KHR_lights_punctual"] = lights_punctual; lights_punctual["light"] = gltf_node->light; } + if (!gltf_node->visible) { + Dictionary khr_node_visibility; + extensions["KHR_node_visibility"] = khr_node_visibility; + khr_node_visibility["visible"] = gltf_node->visible; + if (!p_state->extensions_used.has("KHR_node_visibility")) { + p_state->extensions_used.push_back("KHR_node_visibility"); + if (_visibility_mode == VISIBILITY_MODE_INCLUDE_REQUIRED) { + p_state->extensions_required.push_back("KHR_node_visibility"); + } + } + } if (gltf_node->mesh != -1) { node["mesh"] = gltf_node->mesh; } @@ -617,6 +628,12 @@ Error GLTFDocument::_parse_nodes(Ref p_state) { node->light = light; } } + if (extensions.has("KHR_node_visibility")) { + Dictionary khr_node_visibility = extensions["KHR_node_visibility"]; + if (khr_node_visibility.has("visible")) { + node->visible = khr_node_visibility["visible"]; + } + } for (Ref ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Error err = ext->parse_node_extensions(p_state, node, extensions); @@ -5219,11 +5236,6 @@ Node3D *GLTFDocument::_generate_spatial(Ref p_state, const GLTFNodeIn } void GLTFDocument::_convert_scene_node(Ref p_state, Node *p_current, const GLTFNodeIndex p_gltf_parent, const GLTFNodeIndex p_gltf_root) { - bool retflag = true; - _check_visibility(p_current, retflag); - if (retflag) { - return; - } #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() && p_gltf_root != -1 && p_current->get_owner() == nullptr) { WARN_VERBOSE("glTF export warning: Node '" + p_current->get_name() + "' has no owner. This is likely a temporary node generated by a @tool script. This would not be saved when saving the Godot scene, therefore it will not be exported to glTF."); @@ -5232,6 +5244,13 @@ void GLTFDocument::_convert_scene_node(Ref p_state, Node *p_current, #endif // TOOLS_ENABLED Ref gltf_node; gltf_node.instantiate(); + if (p_current->has_method("is_visible")) { + bool visible = p_current->call("is_visible"); + if (!visible && _visibility_mode == VISIBILITY_MODE_EXCLUDE) { + return; + } + gltf_node->visible = visible; + } gltf_node->set_original_name(p_current->get_name()); gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name())); if (cast_to(p_current)) { @@ -5364,19 +5383,6 @@ void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animatio print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name()); } -void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) { - r_retflag = true; - Node3D *spatial = Object::cast_to(p_node); - Node2D *node_2d = Object::cast_to(p_node); - if (node_2d && !node_2d->is_visible()) { - return; - } - if (spatial && !spatial->is_visible()) { - return; - } - r_retflag = false; -} - void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref p_state, Ref p_gltf_node) { ERR_FAIL_NULL(camera); GLTFCameraIndex camera_index = _convert_camera(p_state, camera); @@ -5654,6 +5660,7 @@ void GLTFDocument::_generate_scene_node(Ref p_state, const GLTFNodeIn if (!gltf_node_name.is_empty()) { current_node->set_name(gltf_node_name); } + current_node->set_visible(gltf_node->visible); // Note: p_scene_parent and p_scene_root must either both be null or both be valid. if (p_scene_root == nullptr) { // If the root node argument is null, this is the root node. @@ -7033,12 +7040,18 @@ void GLTFDocument::_bind_methods() { BIND_ENUM_CONSTANT(ROOT_NODE_MODE_KEEP_ROOT); BIND_ENUM_CONSTANT(ROOT_NODE_MODE_MULTI_ROOT); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_INCLUDE_REQUIRED); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_INCLUDE_OPTIONAL); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_EXCLUDE); + ClassDB::bind_method(D_METHOD("set_image_format", "image_format"), &GLTFDocument::set_image_format); ClassDB::bind_method(D_METHOD("get_image_format"), &GLTFDocument::get_image_format); ClassDB::bind_method(D_METHOD("set_lossy_quality", "lossy_quality"), &GLTFDocument::set_lossy_quality); ClassDB::bind_method(D_METHOD("get_lossy_quality"), &GLTFDocument::get_lossy_quality); ClassDB::bind_method(D_METHOD("set_root_node_mode", "root_node_mode"), &GLTFDocument::set_root_node_mode); ClassDB::bind_method(D_METHOD("get_root_node_mode"), &GLTFDocument::get_root_node_mode); + ClassDB::bind_method(D_METHOD("set_visibility_mode", "visibility_mode"), &GLTFDocument::set_visibility_mode); + ClassDB::bind_method(D_METHOD("get_visibility_mode"), &GLTFDocument::get_visibility_mode); ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "base_path"), &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(String())); ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags"), @@ -7055,6 +7068,7 @@ void GLTFDocument::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_format"), "set_image_format", "get_image_format"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality"); ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_mode"), "set_visibility_mode", "get_visibility_mode"); ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"), &GLTFDocument::register_gltf_document_extension, DEFVAL(false)); @@ -7121,6 +7135,7 @@ HashSet GLTFDocument::get_supported_gltf_extensions_hashset() { supported_extensions.insert("KHR_materials_emissive_strength"); supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); supported_extensions.insert("KHR_materials_unlit"); + supported_extensions.insert("KHR_node_visibility"); supported_extensions.insert("KHR_texture_transform"); for (Ref ext : all_document_extensions) { ERR_CONTINUE(ext.is_null()); @@ -7503,6 +7518,14 @@ GLTFDocument::RootNodeMode GLTFDocument::get_root_node_mode() const { return _root_node_mode; } +void GLTFDocument::set_visibility_mode(VisibilityMode p_visibility_mode) { + _visibility_mode = p_visibility_mode; +} + +GLTFDocument::VisibilityMode GLTFDocument::get_visibility_mode() const { + return _visibility_mode; +} + String GLTFDocument::_gen_unique_name_static(HashSet &r_unique_names, const String &p_name) { const String s_name = p_name.validate_node_name(); diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index b3e6dcf54a6c..55d094d270d0 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -73,6 +73,11 @@ class GLTFDocument : public Resource { ROOT_NODE_MODE_KEEP_ROOT, ROOT_NODE_MODE_MULTI_ROOT, }; + enum VisibilityMode { + VISIBILITY_MODE_INCLUDE_REQUIRED, + VISIBILITY_MODE_INCLUDE_OPTIONAL, + VISIBILITY_MODE_EXCLUDE, + }; private: int _naming_version = 1; @@ -80,6 +85,7 @@ class GLTFDocument : public Resource { float _lossy_quality = 0.75f; Ref _image_save_extension; RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT; + VisibilityMode _visibility_mode = VisibilityMode::VISIBILITY_MODE_INCLUDE_REQUIRED; protected: static void _bind_methods(); @@ -103,6 +109,8 @@ class GLTFDocument : public Resource { float get_lossy_quality() const; void set_root_node_mode(RootNodeMode p_root_node_mode); RootNodeMode get_root_node_mode() const; + void set_visibility_mode(VisibilityMode p_visibility_mode); + VisibilityMode get_visibility_mode() const; static String _gen_unique_name_static(HashSet &r_unique_names, const String &p_name); private: @@ -389,5 +397,6 @@ class GLTFDocument : public Resource { }; VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode); +VARIANT_ENUM_CAST(GLTFDocument::VisibilityMode); #endif // GLTF_DOCUMENT_H diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp index 2934e4b5ee68..8bad6b211fe7 100644 --- a/modules/gltf/structures/gltf_node.cpp +++ b/modules/gltf/structures/gltf_node.cpp @@ -57,6 +57,8 @@ void GLTFNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children); ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light); ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light); + ClassDB::bind_method(D_METHOD("get_visible"), &GLTFNode::get_visible); + ClassDB::bind_method(D_METHOD("set_visible", "visible"), &GLTFNode::set_visible); ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data); ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data); @@ -73,6 +75,7 @@ void GLTFNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); // Vector3 ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "children"), "set_children", "get_children"); // Vector ADD_PROPERTY(PropertyInfo(Variant::INT, "light"), "set_light", "get_light"); // GLTFLightIndex + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "get_visible"); // bool } String GLTFNode::get_original_name() { @@ -178,6 +181,14 @@ void GLTFNode::set_light(GLTFLightIndex p_light) { light = p_light; } +bool GLTFNode::get_visible() { + return visible; +} + +void GLTFNode::set_visible(bool p_visible) { + visible = p_visible; +} + Variant GLTFNode::get_additional_data(const StringName &p_extension_name) { return additional_data[p_extension_name]; } diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h index 63399fb32bc4..9985d4b1015c 100644 --- a/modules/gltf/structures/gltf_node.h +++ b/modules/gltf/structures/gltf_node.h @@ -51,6 +51,7 @@ class GLTFNode : public Resource { GLTFSkinIndex skin = -1; GLTFSkeletonIndex skeleton = -1; bool joint = false; + bool visible = true; Vector children; GLTFLightIndex light = -1; Dictionary additional_data; @@ -101,6 +102,9 @@ class GLTFNode : public Resource { GLTFLightIndex get_light(); void set_light(GLTFLightIndex p_light); + bool get_visible(); + void set_visible(bool p_visible); + Variant get_additional_data(const StringName &p_extension_name); void set_additional_data(const StringName &p_extension_name, Variant p_additional_data); };