Skip to content

Commit 500e0d5

Browse files
committed
Merge pull request #93722 from aaronfranke/gltf-khr-node-visibility
Implement `KHR_node_visibility` in the GLTF module
2 parents 5dc375b + 8459f4c commit 500e0d5

File tree

7 files changed

+108
-18
lines changed

7 files changed

+108
-18
lines changed

modules/gltf/doc_classes/GLTFDocument.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@
133133
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].
134134
[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.
135135
</member>
136+
<member name="visibility_mode" type="int" setter="set_visibility_mode" getter="get_visibility_mode" enum="GLTFDocument.VisibilityMode" default="0">
137+
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.
138+
</member>
136139
</members>
137140
<constants>
138141
<constant name="ROOT_NODE_MODE_SINGLE_ROOT" value="0" enum="RootNodeMode">
@@ -144,5 +147,14 @@
144147
<constant name="ROOT_NODE_MODE_MULTI_ROOT" value="2" enum="RootNodeMode">
145148
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.
146149
</constant>
150+
<constant name="VISIBILITY_MODE_INCLUDE_REQUIRED" value="0" enum="VisibilityMode">
151+
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.
152+
</constant>
153+
<constant name="VISIBILITY_MODE_INCLUDE_OPTIONAL" value="1" enum="VisibilityMode">
154+
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.
155+
</constant>
156+
<constant name="VISIBILITY_MODE_EXCLUDE" value="2" enum="VisibilityMode">
157+
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.4 and earlier. Downside: Invisible nodes will not exist in the exported file.
158+
</constant>
147159
</constants>
148160
</class>

modules/gltf/doc_classes/GLTFNode.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@
8383
<member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1">
8484
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.
8585
</member>
86+
<member name="visible" type="bool" setter="set_visible" getter="get_visible" default="true">
87+
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].
88+
</member>
8689
<member name="xform" type="Transform3D" setter="set_xform" getter="get_xform" default="Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)">
8790
The transform of the glTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred.
8891
</member>

modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ bool EditorSceneExporterGLTFSettings::_set(const StringName &p_name, const Varia
5959
_document->set_root_node_mode((GLTFDocument::RootNodeMode)(int64_t)p_value);
6060
return true;
6161
}
62+
if (p_name == StringName("visibility_mode")) {
63+
_document->set_visibility_mode((GLTFDocument::VisibilityMode)(int64_t)p_value);
64+
return true;
65+
}
6266
return false;
6367
}
6468

@@ -87,6 +91,10 @@ bool EditorSceneExporterGLTFSettings::_get(const StringName &p_name, Variant &r_
8791
r_ret = _document->get_root_node_mode();
8892
return true;
8993
}
94+
if (p_name == StringName("visibility_mode")) {
95+
r_ret = _document->get_visibility_mode();
96+
return true;
97+
}
9098
return false;
9199
}
92100

@@ -156,6 +164,21 @@ String get_friendly_config_prefix(Ref<GLTFDocumentExtension> p_extension) {
156164
return "Unknown GLTFDocumentExtension";
157165
}
158166

167+
bool is_any_node_invisible(Node *p_node) {
168+
if (p_node->has_method("is_visible")) {
169+
bool visible = p_node->call("is_visible");
170+
if (!visible) {
171+
return true;
172+
}
173+
}
174+
for (int i = 0; i < p_node->get_child_count(); i++) {
175+
if (is_any_node_invisible(p_node->get_child(i))) {
176+
return true;
177+
}
178+
}
179+
return false;
180+
}
181+
159182
// Run this before popping up the export settings, because the extensions may have changed.
160183
void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document, Node *p_root) {
161184
_property_list.clear();
@@ -200,6 +223,11 @@ void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p
200223
_property_list.push_back(fallback_image_quality_prop);
201224
PropertyInfo root_node_mode_prop = PropertyInfo(Variant::INT, "root_node_mode", PROPERTY_HINT_ENUM, "Single Root,Keep Root,Multi Root");
202225
_property_list.push_back(root_node_mode_prop);
226+
// If the scene contains any non-visible nodes, show the visibility mode setting.
227+
if (p_root != nullptr && is_any_node_invisible(p_root)) {
228+
PropertyInfo visibility_mode_prop = PropertyInfo(Variant::INT, "visibility_mode", PROPERTY_HINT_ENUM, "Include & Required,Include & Optional,Exclude");
229+
_property_list.push_back(visibility_mode_prop);
230+
}
203231
}
204232

205233
String EditorSceneExporterGLTFSettings::get_copyright() const {

modules/gltf/gltf_document.cpp

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,17 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
443443
extensions["KHR_lights_punctual"] = lights_punctual;
444444
lights_punctual["light"] = gltf_node->light;
445445
}
446+
if (!gltf_node->visible) {
447+
Dictionary khr_node_visibility;
448+
extensions["KHR_node_visibility"] = khr_node_visibility;
449+
khr_node_visibility["visible"] = gltf_node->visible;
450+
if (!p_state->extensions_used.has("KHR_node_visibility")) {
451+
p_state->extensions_used.push_back("KHR_node_visibility");
452+
if (_visibility_mode == VISIBILITY_MODE_INCLUDE_REQUIRED) {
453+
p_state->extensions_required.push_back("KHR_node_visibility");
454+
}
455+
}
456+
}
446457
if (gltf_node->mesh != -1) {
447458
node["mesh"] = gltf_node->mesh;
448459
}
@@ -637,6 +648,12 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
637648
node->light = light;
638649
}
639650
}
651+
if (extensions.has("KHR_node_visibility")) {
652+
Dictionary khr_node_visibility = extensions["KHR_node_visibility"];
653+
if (khr_node_visibility.has("visible")) {
654+
node->visible = khr_node_visibility["visible"];
655+
}
656+
}
640657
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
641658
ERR_CONTINUE(ext.is_null());
642659
Error err = ext->parse_node_extensions(p_state, node, extensions);
@@ -5887,11 +5904,6 @@ Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIn
58875904
}
58885905

58895906
void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current, const GLTFNodeIndex p_gltf_parent, const GLTFNodeIndex p_gltf_root) {
5890-
bool retflag = true;
5891-
_check_visibility(p_current, retflag);
5892-
if (retflag) {
5893-
return;
5894-
}
58955907
#ifdef TOOLS_ENABLED
58965908
if (Engine::get_singleton()->is_editor_hint() && p_gltf_root != -1 && p_current->get_owner() == nullptr) {
58975909
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.");
@@ -5900,6 +5912,13 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
59005912
#endif // TOOLS_ENABLED
59015913
Ref<GLTFNode> gltf_node;
59025914
gltf_node.instantiate();
5915+
if (p_current->has_method("is_visible")) {
5916+
bool visible = p_current->call("is_visible");
5917+
if (!visible && _visibility_mode == VISIBILITY_MODE_EXCLUDE) {
5918+
return;
5919+
}
5920+
gltf_node->visible = visible;
5921+
}
59035922
gltf_node->set_original_name(p_current->get_name());
59045923
gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name()));
59055924
gltf_node->merge_meta_from(p_current);
@@ -6022,19 +6041,6 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd
60226041
#endif // MODULE_CSG_ENABLED
60236042
}
60246043

6025-
void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) {
6026-
r_retflag = true;
6027-
Node3D *spatial = Object::cast_to<Node3D>(p_node);
6028-
Node2D *node_2d = Object::cast_to<Node2D>(p_node);
6029-
if (node_2d && !node_2d->is_visible()) {
6030-
return;
6031-
}
6032-
if (spatial && !spatial->is_visible()) {
6033-
return;
6034-
}
6035-
r_retflag = false;
6036-
}
6037-
60386044
void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) {
60396045
ERR_FAIL_NULL(camera);
60406046
GLTFCameraIndex camera_index = _convert_camera(p_state, camera);
@@ -6318,6 +6324,7 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
63186324
if (!gltf_node_name.is_empty()) {
63196325
current_node->set_name(gltf_node_name);
63206326
}
6327+
current_node->set_visible(gltf_node->visible);
63216328
// Note: p_scene_parent and p_scene_root must either both be null or both be valid.
63226329
if (p_scene_root == nullptr) {
63236330
// If the root node argument is null, this is the root node.
@@ -8208,6 +8215,10 @@ void GLTFDocument::_bind_methods() {
82088215
BIND_ENUM_CONSTANT(ROOT_NODE_MODE_KEEP_ROOT);
82098216
BIND_ENUM_CONSTANT(ROOT_NODE_MODE_MULTI_ROOT);
82108217

8218+
BIND_ENUM_CONSTANT(VISIBILITY_MODE_INCLUDE_REQUIRED);
8219+
BIND_ENUM_CONSTANT(VISIBILITY_MODE_INCLUDE_OPTIONAL);
8220+
BIND_ENUM_CONSTANT(VISIBILITY_MODE_EXCLUDE);
8221+
82118222
ClassDB::bind_method(D_METHOD("set_image_format", "image_format"), &GLTFDocument::set_image_format);
82128223
ClassDB::bind_method(D_METHOD("get_image_format"), &GLTFDocument::get_image_format);
82138224
ClassDB::bind_method(D_METHOD("set_lossy_quality", "lossy_quality"), &GLTFDocument::set_lossy_quality);
@@ -8218,6 +8229,8 @@ void GLTFDocument::_bind_methods() {
82188229
ClassDB::bind_method(D_METHOD("get_fallback_image_quality"), &GLTFDocument::get_fallback_image_quality);
82198230
ClassDB::bind_method(D_METHOD("set_root_node_mode", "root_node_mode"), &GLTFDocument::set_root_node_mode);
82208231
ClassDB::bind_method(D_METHOD("get_root_node_mode"), &GLTFDocument::get_root_node_mode);
8232+
ClassDB::bind_method(D_METHOD("set_visibility_mode", "visibility_mode"), &GLTFDocument::set_visibility_mode);
8233+
ClassDB::bind_method(D_METHOD("get_visibility_mode"), &GLTFDocument::get_visibility_mode);
82218234
ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "base_path"),
82228235
&GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(String()));
82238236
ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags"),
@@ -8236,6 +8249,7 @@ void GLTFDocument::_bind_methods() {
82368249
ADD_PROPERTY(PropertyInfo(Variant::STRING, "fallback_image_format"), "set_fallback_image_format", "get_fallback_image_format");
82378250
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fallback_image_quality"), "set_fallback_image_quality", "get_fallback_image_quality");
82388251
ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode");
8252+
ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_mode"), "set_visibility_mode", "get_visibility_mode");
82398253

82408254
ClassDB::bind_static_method("GLTFDocument", D_METHOD("import_object_model_property", "state", "json_pointer"), &GLTFDocument::import_object_model_property);
82418255
ClassDB::bind_static_method("GLTFDocument", D_METHOD("export_object_model_property", "state", "node_path", "godot_node", "gltf_node_index"), &GLTFDocument::export_object_model_property);
@@ -8306,6 +8320,7 @@ HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() {
83068320
supported_extensions.insert("KHR_materials_emissive_strength");
83078321
supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
83088322
supported_extensions.insert("KHR_materials_unlit");
8323+
supported_extensions.insert("KHR_node_visibility");
83098324
supported_extensions.insert("KHR_texture_transform");
83108325
for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
83118326
ERR_CONTINUE(ext.is_null());
@@ -8706,6 +8721,14 @@ GLTFDocument::RootNodeMode GLTFDocument::get_root_node_mode() const {
87068721
return _root_node_mode;
87078722
}
87088723

8724+
void GLTFDocument::set_visibility_mode(VisibilityMode p_visibility_mode) {
8725+
_visibility_mode = p_visibility_mode;
8726+
}
8727+
8728+
GLTFDocument::VisibilityMode GLTFDocument::get_visibility_mode() const {
8729+
return _visibility_mode;
8730+
}
8731+
87098732
String GLTFDocument::_gen_unique_name_static(HashSet<String> &r_unique_names, const String &p_name) {
87108733
const String s_name = p_name.validate_node_name();
87118734

modules/gltf/gltf_document.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ class GLTFDocument : public Resource {
6060
ROOT_NODE_MODE_KEEP_ROOT,
6161
ROOT_NODE_MODE_MULTI_ROOT,
6262
};
63+
enum VisibilityMode {
64+
VISIBILITY_MODE_INCLUDE_REQUIRED,
65+
VISIBILITY_MODE_INCLUDE_OPTIONAL,
66+
VISIBILITY_MODE_EXCLUDE,
67+
};
6368

6469
private:
6570
int _naming_version = 1;
@@ -69,6 +74,7 @@ class GLTFDocument : public Resource {
6974
float _fallback_image_quality = 0.25f;
7075
Ref<GLTFDocumentExtension> _image_save_extension;
7176
RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT;
77+
VisibilityMode _visibility_mode = VisibilityMode::VISIBILITY_MODE_INCLUDE_REQUIRED;
7278

7379
protected:
7480
static void _bind_methods();
@@ -100,6 +106,8 @@ class GLTFDocument : public Resource {
100106
float get_fallback_image_quality() const;
101107
void set_root_node_mode(RootNodeMode p_root_node_mode);
102108
RootNodeMode get_root_node_mode() const;
109+
void set_visibility_mode(VisibilityMode p_visibility_mode);
110+
VisibilityMode get_visibility_mode() const;
103111
static String _gen_unique_name_static(HashSet<String> &r_unique_names, const String &p_name);
104112

105113
private:
@@ -385,3 +393,4 @@ class GLTFDocument : public Resource {
385393
};
386394

387395
VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode);
396+
VARIANT_ENUM_CAST(GLTFDocument::VisibilityMode);

modules/gltf/structures/gltf_node.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ void GLTFNode::_bind_methods() {
6060
ClassDB::bind_method(D_METHOD("append_child_index", "child_index"), &GLTFNode::append_child_index);
6161
ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light);
6262
ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light);
63+
ClassDB::bind_method(D_METHOD("get_visible"), &GLTFNode::get_visible);
64+
ClassDB::bind_method(D_METHOD("set_visible", "visible"), &GLTFNode::set_visible);
6365
ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data);
6466
ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data);
6567
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() {
7779
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); // Vector3
7880
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "children"), "set_children", "get_children"); // Vector<int>
7981
ADD_PROPERTY(PropertyInfo(Variant::INT, "light"), "set_light", "get_light"); // GLTFLightIndex
82+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "get_visible"); // bool
8083
}
8184

8285
String GLTFNode::get_original_name() {
@@ -186,6 +189,14 @@ void GLTFNode::set_light(GLTFLightIndex p_light) {
186189
light = p_light;
187190
}
188191

192+
bool GLTFNode::get_visible() {
193+
return visible;
194+
}
195+
196+
void GLTFNode::set_visible(bool p_visible) {
197+
visible = p_visible;
198+
}
199+
189200
Variant GLTFNode::get_additional_data(const StringName &p_extension_name) {
190201
return additional_data[p_extension_name];
191202
}

modules/gltf/structures/gltf_node.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class GLTFNode : public Resource {
5050
GLTFSkinIndex skin = -1;
5151
GLTFSkeletonIndex skeleton = -1;
5252
bool joint = false;
53+
bool visible = true;
5354
Vector<int> children;
5455
GLTFLightIndex light = -1;
5556
Dictionary additional_data;
@@ -101,6 +102,9 @@ class GLTFNode : public Resource {
101102
GLTFLightIndex get_light();
102103
void set_light(GLTFLightIndex p_light);
103104

105+
bool get_visible();
106+
void set_visible(bool p_visible);
107+
104108
Variant get_additional_data(const StringName &p_extension_name);
105109
bool has_additional_data(const StringName &p_extension_name);
106110
void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);

0 commit comments

Comments
 (0)