From d4c18160993acd25022d5a465806093a3ff4a8b8 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Fri, 28 Jun 2024 14:06:29 -0700 Subject: [PATCH] Implement KHR_node_visibility in the GLTF module --- modules/gltf/doc_classes/GLTFDocument.xml | 12 ++++ modules/gltf/doc_classes/GLTFNode.xml | 3 + .../editor_scene_exporter_gltf_settings.cpp | 28 +++++++++ modules/gltf/gltf_document.cpp | 59 +++++++++++++------ modules/gltf/gltf_document.h | 9 +++ modules/gltf/structures/gltf_node.cpp | 11 ++++ modules/gltf/structures/gltf_node.h | 4 ++ 7 files changed, 108 insertions(+), 18 deletions(-) diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index ffc3ab926c77..5a0f6d21cc24 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -126,6 +126,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. + @@ -137,5 +140,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 eb92723a06d4..e87d88f65c3a 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -83,6 +83,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_settings.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp index c14e92c3a0f6..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,6 +136,21 @@ 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, Node *p_root) { _property_list.clear(); @@ -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/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 571a04936da0..81ff723c0450 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -434,6 +434,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; } @@ -629,6 +640,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); @@ -5801,11 +5818,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."); @@ -5814,6 +5826,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())); gltf_node->merge_meta_from(p_current); @@ -5934,19 +5953,6 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd } #endif // MODULE_CSG_ENABLED -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); @@ -6228,6 +6234,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. @@ -8118,12 +8125,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"), @@ -8140,6 +8153,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("import_object_model_property", "state", "json_pointer"), &GLTFDocument::import_object_model_property); ClassDB::bind_static_method("GLTFDocument", D_METHOD("export_object_model_property", "state", "node_path", "godot_node", "gltf_node_index"), &GLTFDocument::export_object_model_property); @@ -8210,6 +8224,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()); @@ -8605,6 +8620,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 a6d6caa3f0a7..3e9070ed99ff 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -66,6 +66,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; @@ -73,6 +78,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(); @@ -100,6 +106,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: @@ -388,5 +396,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 1626313551ef..8a42ae3e8d86 100644 --- a/modules/gltf/structures/gltf_node.cpp +++ b/modules/gltf/structures/gltf_node.cpp @@ -60,6 +60,8 @@ void GLTFNode::_bind_methods() { ClassDB::bind_method(D_METHOD("append_child_index", "child_index"), &GLTFNode::append_child_index); 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); ClassDB::bind_method(D_METHOD("get_scene_node_path", "gltf_state", "handle_skeletons"), &GLTFNode::get_scene_node_path, DEFVAL(true)); @@ -77,6 +79,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() { @@ -186,6 +189,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 f72b65a00360..9c2377658393 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; @@ -102,6 +103,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); bool has_additional_data(const StringName &p_extension_name); void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);