Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GLTF: Add root node export options and GODOT_single_root extension #81851

Merged
merged 1 commit into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions modules/gltf/doc_classes/GLTFDocument.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,20 @@
<member name="lossy_quality" type="float" setter="set_lossy_quality" getter="get_lossy_quality" default="0.75">
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.
</member>
<member name="root_node_mode" type="int" setter="set_root_node_mode" getter="get_root_node_mode" enum="GLTFDocument.RootNodeMode" default="0">
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>
</members>
<constants>
<constant name="ROOT_NODE_MODE_SINGLE_ROOT" value="0" enum="RootNodeMode">
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].
</constant>
<constant name="ROOT_NODE_MODE_KEEP_ROOT" value="1" enum="RootNodeMode">
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.
</constant>
<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>
</constants>
</class>
1 change: 1 addition & 0 deletions modules/gltf/doc_classes/GLTFDocumentExtension.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<description>
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.
</description>
</method>
<method name="_get_image_file_extension" qualifiers="virtual">
Expand Down
1 change: 0 additions & 1 deletion modules/gltf/extensions/gltf_document_extension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Di
Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> 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;
Expand Down
52 changes: 45 additions & 7 deletions modules/gltf/gltf_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5883,14 +5883,18 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> 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);
Comment on lines 5893 to 5895
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An if statement got lost here.

It's important that the call to set_owner specifically still be guarded with a check that (current_node != p_scene_root)

Otherwise if you call scene_root.set_owner(scene_root) it will print an error or malfunction.

If you are 100% certain that current_node is never the same as p_scene_root in any of the 3 export modes, it would help if you wrote an explanation because it feels like it could happen especially in the single root case.

Copy link
Member Author

@aaronfranke aaronfranke Sep 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't lost, just changed. It won't ever happen because p_scene_root will either be null or different from current_node. When generating the scene root node, we pass in null to this function.

I already have a comment above "If the root node argument is null, this is the root node", is this not enough?

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) {
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -7336,9 +7347,15 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_
}

Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> 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.
Expand Down Expand Up @@ -7406,6 +7423,19 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> 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;
}
Expand Down Expand Up @@ -7598,3 +7628,11 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> 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;
}
22 changes: 16 additions & 6 deletions modules/gltf/gltf_document.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ class GLTFDocument : public Resource {
static Vector<Ref<GLTFDocumentExtension>> all_document_extensions;
Vector<Ref<GLTFDocumentExtension>> document_extensions;

private:
const float BAKE_FPS = 30.0f;
String _image_format = "PNG";
float _lossy_quality = 0.75f;
Ref<GLTFDocumentExtension> _image_save_extension;

public:
const int32_t JOINT_GROUP_SIZE = 4;

Expand All @@ -71,6 +65,18 @@ class GLTFDocument : public Resource {
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<GLTFDocumentExtension> _image_save_extension;
RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT;

protected:
static void _bind_methods();
Expand All @@ -84,6 +90,8 @@ class GLTFDocument : public Resource {
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<GLTFState> p_state);
Expand Down Expand Up @@ -379,4 +387,6 @@ class GLTFDocument : public Resource {
Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file);
};

VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode);

#endif // GLTF_DOCUMENT_H
Loading