diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 9b84397c7ed..5c10b76e0ab 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -95,5 +95,20 @@
If [member image_format] is a lossy image format, this determines the lossy quality of the image. On a range of [code]0.0[/code] to [code]1.0[/code], where [code]0.0[/code] is the lowest quality and [code]1.0[/code] is the highest quality. A lossy quality of [code]1.0[/code] is not the same as lossless.
+
+ 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.
+
+
+
+ Treat the Godot scene's root node as the root node of the glTF file, and mark it as the single root node via the [code]GODOT_single_root[/code] glTF extension. This will be parsed the same as [constant ROOT_NODE_MODE_KEEP_ROOT] if the implementation does not support [code]GODOT_single_root[/code].
+
+
+ Treat the Godot scene's root node as the root node of the glTF file, but do not mark it as anything special. An extra root node will be generated when importing into Godot. This uses only vanilla glTF features. This is equivalent to the behavior in Godot 4.1 and earlier.
+
+
+ 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.
+
+
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index dbfbf63da6c..eee62845ca5 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -65,6 +65,7 @@
Part of the import process. This method is run after [method _import_post_parse] and before [method _import_node].
Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node.
+ [b]Note:[/b] The [param scene_parent] parameter may be null if this is the single root node.
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index e16e3666920..582bcf466b5 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -101,7 +101,6 @@ Error GLTFDocumentExtension::parse_texture_json(Ref p_state, const Di
Node3D *GLTFDocumentExtension::generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent) {
ERR_FAIL_NULL_V(p_state, nullptr);
ERR_FAIL_NULL_V(p_gltf_node, nullptr);
- ERR_FAIL_NULL_V(p_scene_parent, nullptr);
Node3D *ret_node = nullptr;
GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node);
return ret_node;
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index cf57cb2b73e..cee65d70c9c 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -5883,14 +5883,18 @@ void GLTFDocument::_generate_scene_node(Ref p_state, const GLTFNodeIn
if (!gltf_node_name.is_empty()) {
current_node->set_name(gltf_node_name);
}
- // Add the node we generated and set the owner to the scene root.
- p_scene_parent->add_child(current_node, true);
- if (current_node != p_scene_root) {
+ // 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.
+ p_scene_root = current_node;
+ } else {
+ // Add the node we generated and set the owner to the scene root.
+ p_scene_parent->add_child(current_node, true);
Array args;
args.append(p_scene_root);
current_node->propagate_call(StringName("set_owner"), args);
+ current_node->set_transform(gltf_node->xform);
}
- current_node->set_transform(gltf_node->xform);
p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
@@ -7219,13 +7223,20 @@ void GLTFDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"),
&GLTFDocument::write_to_filesystem);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_SINGLE_ROOT);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_KEEP_ROOT);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_MULTI_ROOT);
+
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);
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");
ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"),
&GLTFDocument::register_gltf_document_extension, DEFVAL(false));
@@ -7336,9 +7347,15 @@ Error GLTFDocument::write_to_filesystem(Ref p_state, const String &p_
}
Node *GLTFDocument::_generate_scene_node_tree(Ref p_state) {
- Node *single_root = memnew(Node3D);
- for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) {
- _generate_scene_node(p_state, p_state->root_nodes[root_i], single_root, single_root);
+ Node *single_root;
+ if (p_state->extensions_used.has("GODOT_single_root")) {
+ _generate_scene_node(p_state, 0, nullptr, nullptr);
+ single_root = p_state->scene_nodes[0];
+ } else {
+ single_root = memnew(Node3D);
+ for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) {
+ _generate_scene_node(p_state, p_state->root_nodes[root_i], single_root, single_root);
+ }
}
// Assign the scene name and single root name to each other
// if one is missing, or do nothing if both are already set.
@@ -7406,6 +7423,19 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref p_state, uint
}
}
// Add the root node(s) and their descendants to the state.
+ if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_MULTI_ROOT) {
+ const int child_count = p_node->get_child_count();
+ if (child_count > 0) {
+ for (int i = 0; i < child_count; i++) {
+ _convert_scene_node(p_state, p_node->get_child(i), -1, -1);
+ }
+ p_state->scene_name = p_node->get_name();
+ return OK;
+ }
+ }
+ if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT) {
+ p_state->extensions_used.append("GODOT_single_root");
+ }
_convert_scene_node(p_state, p_node, -1, -1);
return OK;
}
@@ -7598,3 +7628,11 @@ Error GLTFDocument::_parse_gltf_extensions(Ref p_state) {
}
return ret;
}
+
+void GLTFDocument::set_root_node_mode(GLTFDocument::RootNodeMode p_root_node_mode) {
+ _root_node_mode = p_root_node_mode;
+}
+
+GLTFDocument::RootNodeMode GLTFDocument::get_root_node_mode() const {
+ return _root_node_mode;
+}
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index febe04b55ff..828d650cff3 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -40,12 +40,6 @@ class GLTFDocument : public Resource {
static Vector[> all_document_extensions;
Vector][> document_extensions;
-private:
- const float BAKE_FPS = 30.0f;
- String _image_format = "PNG";
- float _lossy_quality = 0.75f;
- Ref _image_save_extension;
-
public:
const int32_t JOINT_GROUP_SIZE = 4;
@@ -71,6 +65,18 @@ public:
TEXTURE_TYPE_GENERIC = 0,
TEXTURE_TYPE_NORMAL = 1,
};
+ enum RootNodeMode {
+ ROOT_NODE_MODE_SINGLE_ROOT,
+ ROOT_NODE_MODE_KEEP_ROOT,
+ ROOT_NODE_MODE_MULTI_ROOT,
+ };
+
+private:
+ const float BAKE_FPS = 30.0f;
+ String _image_format = "PNG";
+ float _lossy_quality = 0.75f;
+ Ref _image_save_extension;
+ RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT;
protected:
static void _bind_methods();
@@ -84,6 +90,8 @@ public:
String get_image_format() const;
void set_lossy_quality(float p_lossy_quality);
float get_lossy_quality() const;
+ void set_root_node_mode(RootNodeMode p_root_node_mode);
+ RootNodeMode get_root_node_mode() const;
private:
void _build_parent_hierachy(Ref p_state);
@@ -379,4 +387,6 @@ public:
Error _parse(Ref p_state, String p_path, Ref p_file);
};
+VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode);
+
#endif // GLTF_DOCUMENT_H
]