Implement KHR_node_visibility in the GLTF module

This commit is contained in:
Aaron Franke 2024-06-28 14:06:29 -07:00
parent 215acd52e8
commit 8459f4cdaf
No known key found for this signature in database
GPG key ID: 40A1750B977E56BF
7 changed files with 108 additions and 18 deletions

View file

@ -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.
</member>
<member name="visibility_mode" type="int" setter="set_visibility_mode" getter="get_visibility_mode" enum="GLTFDocument.VisibilityMode" default="0">
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.
</member>
</members>
<constants>
<constant name="ROOT_NODE_MODE_SINGLE_ROOT" value="0" enum="RootNodeMode">
@ -137,5 +140,14 @@
<constant name="ROOT_NODE_MODE_MULTI_ROOT" value="2" enum="RootNodeMode">
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.
</constant>
<constant name="VISIBILITY_MODE_INCLUDE_REQUIRED" value="0" enum="VisibilityMode">
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.
</constant>
<constant name="VISIBILITY_MODE_INCLUDE_OPTIONAL" value="1" enum="VisibilityMode">
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.
</constant>
<constant name="VISIBILITY_MODE_EXCLUDE" value="2" enum="VisibilityMode">
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.
</constant>
</constants>
</class>

View file

@ -83,6 +83,9 @@
<member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1">
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.
</member>
<member name="visible" type="bool" setter="set_visible" getter="get_visible" default="true">
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].
</member>
<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)">
The transform of the glTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred.
</member>

View file

@ -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<GLTFDocumentExtension> 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<GLTFDocument> p_document, Node *p_root) {
_property_list.clear();
@ -168,6 +191,11 @@ void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> 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 {

View file

@ -443,6 +443,17 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> 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;
}
@ -637,6 +648,12 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> 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<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
Error err = ext->parse_node_extensions(p_state, node, extensions);
@ -5844,11 +5861,6 @@ Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIn
}
void GLTFDocument::_convert_scene_node(Ref<GLTFState> 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.");
@ -5857,6 +5869,13 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
#endif // TOOLS_ENABLED
Ref<GLTFNode> 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);
@ -5979,19 +5998,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<Node3D>(p_node);
Node2D *node_2d = Object::cast_to<Node2D>(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<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) {
ERR_FAIL_NULL(camera);
GLTFCameraIndex camera_index = _convert_camera(p_state, camera);
@ -6275,6 +6281,7 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> 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.
@ -8165,12 +8172,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"),
@ -8187,6 +8200,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);
@ -8257,6 +8271,7 @@ HashSet<String> 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<GLTFDocumentExtension> ext : all_document_extensions) {
ERR_CONTINUE(ext.is_null());
@ -8657,6 +8672,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<String> &r_unique_names, const String &p_name) {
const String s_name = p_name.validate_node_name();

View file

@ -60,6 +60,11 @@ public:
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;
@ -67,6 +72,7 @@ private:
float _lossy_quality = 0.75f;
Ref<GLTFDocumentExtension> _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();
@ -94,6 +100,8 @@ public:
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<String> &r_unique_names, const String &p_name);
private:
@ -378,3 +386,4 @@ public:
};
VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode);
VARIANT_ENUM_CAST(GLTFDocument::VisibilityMode);

View file

@ -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<int>
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];
}

View file

@ -50,6 +50,7 @@ private:
GLTFSkinIndex skin = -1;
GLTFSkeletonIndex skeleton = -1;
bool joint = false;
bool visible = true;
Vector<int> children;
GLTFLightIndex light = -1;
Dictionary additional_data;
@ -101,6 +102,9 @@ public:
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);