From 62e10daf7039da28c6fd8c8256b1fe91544156ee Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Sat, 10 Dec 2022 14:09:56 -0600 Subject: [PATCH] [3.x] Backport the GLTFDocumentExtension system --- modules/gltf/config.py | 1 + modules/gltf/doc_classes/GLTFDocument.xml | 22 ++ .../doc_classes/GLTFDocumentExtension.xml | 118 +++++++++ modules/gltf/doc_classes/GLTFNode.xml | 20 ++ modules/gltf/doc_classes/GLTFState.xml | 25 ++ .../extensions/gltf_document_extension.cpp | 171 +++++++++++++ .../gltf/extensions/gltf_document_extension.h | 60 +++++ modules/gltf/gltf_defines.h | 1 + modules/gltf/gltf_document.cpp | 230 +++++++++++++++--- modules/gltf/gltf_document.h | 18 +- modules/gltf/gltf_state.cpp | 22 ++ modules/gltf/gltf_state.h | 8 + modules/gltf/packed_scene_gltf.cpp | 1 + modules/gltf/register_types.cpp | 2 + modules/gltf/structures/gltf_node.cpp | 10 + modules/gltf/structures/gltf_node.h | 4 + 16 files changed, 680 insertions(+), 33 deletions(-) create mode 100644 modules/gltf/doc_classes/GLTFDocumentExtension.xml create mode 100644 modules/gltf/extensions/gltf_document_extension.cpp create mode 100644 modules/gltf/extensions/gltf_document_extension.h diff --git a/modules/gltf/config.py b/modules/gltf/config.py index 41b76769b54..c98e12be4be 100644 --- a/modules/gltf/config.py +++ b/modules/gltf/config.py @@ -14,6 +14,7 @@ def get_doc_classes(): "GLTFBufferView", "GLTFCamera", "GLTFDocument", + "GLTFDocumentExtension", "GLTFLight", "GLTFMesh", "GLTFNode", diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 7d0840bb09c..3f7f86e1dca 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -8,6 +8,28 @@ + + + + + + Registers the given [GLTFDocumentExtension] instance with GLTFDocument. If [code]first_priority[/code] is true, this extension will be run first. Otherwise, it will be run last. + [b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode]. + + + + + + Unregisters all [GLTFDocumentExtension] instances. + + + + + + + Unregisters the given [GLTFDocumentExtension] instance. + + diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml new file mode 100644 index 00000000000..457257d3d2f --- /dev/null +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -0,0 +1,118 @@ + + + + [GLTFDocument] extension class. + + + Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of GLTF import or export. + [b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode]. + + + + + + + + + + + Part of the export process. This method is run after [method _export_preflight] and before [method _export_node]. + Runs when converting the data from a Godot scene node. This method can be used to process the Godot scene node data into a format that can be used by [method _export_node]. + + + + + + + + + + Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_post]. + This method can be used to modify the final JSON of each node. + + + + + + + Part of the export process. This method is run last, after all other parts of the export process. + This method can be used to modify the final JSON of the generated GLTF file. + + + + + + + + Part of the export process. This method is run first, before all other parts of the export process. + The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given GLTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. + + + + + + + + + Part of the import process. This method is run after [method _parse_node_extensions] and before [method _import_post_parse]. + 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. + + + + + + Part of the import process. This method is run after [method _import_preflight] and before [method _parse_node_extensions]. + Returns an array of the GLTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a GLTF file with required extensions can be loaded. + + + + + + + + + + Part of the import process. This method is run after [method _import_post_parse] and before [method _import_post]. + This method can be used to make modifications to each of the generated Godot scene nodes. + + + + + + + + Part of the import process. This method is run last, after all other parts of the import process. + This method can be used to modify the final Godot scene generated by the import process. + + + + + + + Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_node]. + This method can be used to modify any of the data imported so far, including any scene nodes, before running the final per-node import step. + + + + + + + + Part of the import process. This method is run first, before all other parts of the import process. + The return value is used to determine if this [GLTFDocumentExtension] instance should be used for importing a given GLTF file. If [constant OK], the import will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. + + + + + + + + + Part of the import process. This method is run after [method _get_supported_extensions] and before [method _generate_scene_node]. + Runs when parsing the node extensions of a GLTFNode. This method can be used to process the extension JSON data into a format that can be used by [method _generate_scene_node]. The return value should be a member of the [enum Error] enum. + + + + + + diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index d986a46c56b..80bfba47aa7 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -1,13 +1,33 @@ + GLTF node class. + Represents a GLTF node. GLTF nodes may have names, transforms, children (other GLTF nodes), and more specialized properties (represented by their own classes). [b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFNode] within a script will cause an error in an exported project. + https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md" + + + + + Gets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + + + + + + + + Sets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + + diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 5eb7be2e5b8..74f005e3023 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -8,11 +8,27 @@ + + + + + + Appends an extension to the list of extensions used by this GLTF file during serialization. If [code]required[/code] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically. + + + + + + + Gets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + + @@ -113,6 +129,15 @@ + + + + + + Sets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + + diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp new file mode 100644 index 00000000000..f0b2a162b7c --- /dev/null +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -0,0 +1,171 @@ +/*************************************************************************/ +/* gltf_document_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_document_extension.h" + +#include "../gltf_document.h" + +void GLTFDocumentExtension::_bind_methods() { + // Import process. + BIND_VMETHOD(MethodInfo(Variant::INT, "_import_preflight", PropertyInfo(Variant::OBJECT, "state"), PropertyInfo(Variant::POOL_STRING_ARRAY, "extensions"))); + BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_supported_extensions")); + BIND_VMETHOD(MethodInfo(Variant::INT, "_parse_node_extensions", PropertyInfo(Variant::OBJECT, "state"), PropertyInfo(Variant::OBJECT, "gltf_node"), PropertyInfo(Variant::DICTIONARY, "extensions"))); + BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_generate_scene_node", PropertyInfo(Variant::OBJECT, "state"), PropertyInfo(Variant::OBJECT, "gltf_node"), PropertyInfo(Variant::OBJECT, "scene_parent"))); + BIND_VMETHOD(MethodInfo(Variant::INT, "_import_post_parse", PropertyInfo(Variant::OBJECT, "state"))); + BIND_VMETHOD(MethodInfo(Variant::INT, "_import_node", PropertyInfo(Variant::OBJECT, "state"), PropertyInfo(Variant::OBJECT, "gltf_node"), PropertyInfo(Variant::DICTIONARY, "json"), PropertyInfo(Variant::OBJECT, "node"))); + BIND_VMETHOD(MethodInfo(Variant::INT, "_import_post", PropertyInfo(Variant::OBJECT, "state"), PropertyInfo(Variant::OBJECT, "root"))); + // Export process. + BIND_VMETHOD(MethodInfo(Variant::INT, "_export_preflight", PropertyInfo(Variant::OBJECT, "state"), PropertyInfo(Variant::OBJECT, "root"))); + BIND_VMETHOD(MethodInfo("_convert_scene_node", PropertyInfo(Variant::OBJECT, "state"), PropertyInfo(Variant::OBJECT, "gltf_node"), PropertyInfo(Variant::OBJECT, "scene_node"))); + BIND_VMETHOD(MethodInfo(Variant::INT, "_export_node", PropertyInfo(Variant::OBJECT, "state"), PropertyInfo(Variant::OBJECT, "gltf_node"), PropertyInfo(Variant::DICTIONARY, "json"), PropertyInfo(Variant::OBJECT, "node"))); + BIND_VMETHOD(MethodInfo(Variant::INT, "_export_post", PropertyInfo(Variant::OBJECT, "state"))); +} + +// Import process. +Error GLTFDocumentExtension::import_preflight(Ref p_state, Vector p_extensions) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ScriptInstance *si = get_script_instance(); + if (!si) { + return Error::OK; + } + int err = si->call("_import_preflight", p_state, p_extensions); + return Error(err); +} + +Vector GLTFDocumentExtension::get_supported_extensions() { + Vector ret; + ScriptInstance *si = get_script_instance(); + if (!si) { + return ret; + } + si->call("_get_supported_extensions"); + return ret; +} + +Error GLTFDocumentExtension::parse_node_extensions(Ref p_state, Ref p_gltf_node, Dictionary &p_extensions) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); + ScriptInstance *si = get_script_instance(); + if (!si) { + return Error::OK; + } + int err = si->call("_parse_node_extensions", p_state, p_gltf_node, p_extensions); + return Error(err); +} + +Spatial *GLTFDocumentExtension::generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent) { + ERR_FAIL_COND_V(p_state.is_null(), nullptr); + ERR_FAIL_COND_V(p_gltf_node.is_null(), nullptr); + ERR_FAIL_NULL_V(p_scene_parent, nullptr); + ScriptInstance *si = get_script_instance(); + if (!si) { + return nullptr; + } + Variant ret = si->call("_generate_scene_node", p_state, p_gltf_node, p_scene_parent); + Spatial *ret_node = cast_to(ret.operator Object *()); + return ret_node; +} + +Error GLTFDocumentExtension::import_post_parse(Ref p_state) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ScriptInstance *si = get_script_instance(); + if (!si) { + return Error::OK; + } + int err = si->call("_import_post_parse", p_state); + return Error(err); +} + +Error GLTFDocumentExtension::import_node(Ref p_state, Ref p_gltf_node, Dictionary &r_dict, Node *p_node) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); + ScriptInstance *si = get_script_instance(); + if (!si) { + return Error::OK; + } + int err = si->call("_import_node", p_state, p_gltf_node, r_dict, p_node); + return Error(err); +} + +Error GLTFDocumentExtension::import_post(Ref p_state, Node *p_root) { + ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ScriptInstance *si = get_script_instance(); + if (!si) { + return Error::OK; + } + int err = si->call("_import_post", p_state, p_root); + return Error(err); +} + +// Export process. +Error GLTFDocumentExtension::export_preflight(Ref p_state, Node *p_root) { + ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); + ScriptInstance *si = get_script_instance(); + if (!si) { + return Error::OK; + } + int err = si->call("_export_preflight", p_root); + return Error(err); +} + +void GLTFDocumentExtension::convert_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_node) { + ERR_FAIL_NULL(p_state.is_null()); + ERR_FAIL_NULL(p_gltf_node.is_null()); + ERR_FAIL_NULL(p_scene_node); + ScriptInstance *si = get_script_instance(); + if (!si) { + return; + } + si->call("_convert_scene_node", p_state, p_gltf_node, p_scene_node); +} + +Error GLTFDocumentExtension::export_node(Ref p_state, Ref p_gltf_node, Dictionary &r_dict, Node *p_node) { + ERR_FAIL_NULL_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); + ScriptInstance *si = get_script_instance(); + if (!si) { + return Error::OK; + } + int err = si->call("_export_node", p_state, p_gltf_node, r_dict, p_node); + return Error(err); +} + +Error GLTFDocumentExtension::export_post(Ref p_state) { + ERR_FAIL_NULL_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ScriptInstance *si = get_script_instance(); + if (!si) { + return Error::OK; + } + int err = si->call("_export_post", p_state); + return Error(err); +} diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h new file mode 100644 index 00000000000..13af567ec19 --- /dev/null +++ b/modules/gltf/extensions/gltf_document_extension.h @@ -0,0 +1,60 @@ +/*************************************************************************/ +/* gltf_document_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_DOCUMENT_EXTENSION_H +#define GLTF_DOCUMENT_EXTENSION_H + +#include "../gltf_state.h" + +#include "scene/3d/spatial.h" + +class GLTFDocumentExtension : public Resource { + GDCLASS(GLTFDocumentExtension, Resource); + +protected: + static void _bind_methods(); + +public: + // Import process. + virtual Error import_preflight(Ref p_state, Vector p_extensions); + virtual Vector get_supported_extensions(); + virtual Error parse_node_extensions(Ref p_state, Ref p_gltf_node, Dictionary &p_extensions); + virtual Spatial *generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent); + virtual Error import_post_parse(Ref p_state); + virtual Error import_node(Ref p_state, Ref p_gltf_node, Dictionary &r_json, Node *p_node); + virtual Error import_post(Ref p_state, Node *p_node); + // Export process. + virtual Error export_preflight(Ref p_state, Node *p_root); + virtual void convert_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_node); + virtual Error export_node(Ref p_state, Ref p_gltf_node, Dictionary &r_json, Node *p_node); + virtual Error export_post(Ref p_state); +}; + +#endif // GLTF_DOCUMENT_EXTENSION_H diff --git a/modules/gltf/gltf_defines.h b/modules/gltf/gltf_defines.h index aa48caa05cd..ae4d96ddfca 100644 --- a/modules/gltf/gltf_defines.h +++ b/modules/gltf/gltf_defines.h @@ -50,6 +50,7 @@ class GLTFAnimation; class GLTFBufferView; class GLTFCamera; class GLTFDocument; +class GLTFDocumentExtension; class GLTFLight; class GLTFMesh; class GLTFNode; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 4402fe99b0a..6933a575df1 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -90,6 +90,15 @@ Error GLTFDocument::serialize(Ref p_state, Node *p_root, const String p_state->skeleton3d_to_gltf_skeleton.clear(); p_state->skin_and_skeleton3d_to_gltf_skin.clear(); + document_extensions.clear(); + for (int ext_i = 0; ext_i < all_document_extensions.size(); ext_i++) { + Ref ext = all_document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + Error err = ext->export_preflight(p_state, p_root); + if (err == OK) { + document_extensions.push_back(ext); + } + } _convert_scene_node(p_state, p_root, -1, -1); if (!p_state->buffers.size()) { p_state->buffers.push_back(Vector()); @@ -175,7 +184,7 @@ Error GLTFDocument::serialize(Ref p_state, Node *p_root, const String } /* STEP 17 SERIALIZE EXTENSIONS */ - err = _serialize_extensions(p_state); + err = _serialize_gltf_extensions(p_state); if (err != OK) { return Error::FAILED; } @@ -191,6 +200,12 @@ Error GLTFDocument::serialize(Ref p_state, Node *p_root, const String if (err != OK) { return Error::FAILED; } + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + err = ext->export_post(p_state); + ERR_FAIL_COND_V(err != OK, err); + } uint64_t elapsed = OS::get_singleton()->get_ticks_usec() - begin_time; float elapsed_sec = double(elapsed) / 1000000.0; elapsed_sec = Math::stepify(elapsed_sec, 0.01f); @@ -199,9 +214,9 @@ Error GLTFDocument::serialize(Ref p_state, Node *p_root, const String return OK; } -Error GLTFDocument::_serialize_extensions(Ref p_state) const { - Array extensions_used; - Array extensions_required; +Error GLTFDocument::_serialize_gltf_extensions(Ref p_state) const { + Vector extensions_used = p_state->extensions_used; + Vector extensions_required = p_state->extensions_required; if (!p_state->lights.empty()) { extensions_used.push_back("KHR_lights_punctual"); } @@ -210,9 +225,11 @@ Error GLTFDocument::_serialize_extensions(Ref p_state) const { extensions_required.push_back("KHR_texture_transform"); } if (!extensions_used.empty()) { + extensions_used.sort(); p_state->json["extensionsUsed"] = extensions_used; } if (!extensions_required.empty()) { + extensions_required.sort(); p_state->json["extensionsRequired"] = extensions_required; } return OK; @@ -433,6 +450,13 @@ Error GLTFDocument::_serialize_nodes(Ref p_state) { } node["children"] = children; } + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + ERR_CONTINUE(!p_state->scene_nodes.find(i)); + Error err = ext->export_node(p_state, gltf_node, node, p_state->scene_nodes[i]); + ERR_CONTINUE(err != OK); + } nodes.push_back(node); } p_state->json["nodes"] = nodes; @@ -647,6 +671,12 @@ Error GLTFDocument::_parse_nodes(Ref p_state) { node->light = light; } } + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + Error err = ext->parse_node_extensions(p_state, node, extensions); + ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + p_state->filename + ". Continuing."); + } } if (n.has("children")) { @@ -5294,6 +5324,11 @@ void GLTFDocument::_convert_scene_node(Ref p_state, Node *p_current, AnimationPlayer *animation_player = Object::cast_to(p_current); _convert_animation_player_to_gltf(animation_player, p_state, p_gltf_parent, p_gltf_root, gltf_node, p_current); } + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + ext->convert_scene_node(p_state, gltf_node, p_current); + } GLTFNodeIndex current_node_i = p_state->nodes.size(); GLTFNodeIndex gltf_root = p_gltf_root; if (gltf_root == -1) { @@ -5574,22 +5609,33 @@ void GLTFDocument::_generate_scene_node(Ref p_state, Node *p_scene_pa // and attach it to the bone_attachment p_scene_parent = bone_attachment; } - if (gltf_node->mesh >= 0) { - current_node = _generate_mesh_instance(p_state, p_scene_parent, p_node_index); - } else if (gltf_node->camera >= 0) { - current_node = _generate_camera(p_state, p_scene_parent, p_node_index); - } else if (gltf_node->light >= 0) { - current_node = _generate_light(p_state, p_scene_parent, p_node_index); + // Check if any GLTFDocumentExtension classes want to generate a node for us. + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + current_node = ext->generate_scene_node(p_state, gltf_node, p_scene_parent); + if (current_node) { + break; + } } - - // We still have not managed to make a node. + // If none of our GLTFDocumentExtension classes generated us a node, we generate one. if (!current_node) { - current_node = _generate_spatial(p_state, p_scene_parent, p_node_index); + if (gltf_node->mesh >= 0) { + current_node = _generate_mesh_instance(p_state, p_scene_parent, p_node_index); + } else if (gltf_node->camera >= 0) { + current_node = _generate_camera(p_state, p_scene_parent, p_node_index); + } else if (gltf_node->light >= 0) { + current_node = _generate_light(p_state, p_scene_parent, p_node_index); + } else { + current_node = _generate_spatial(p_state, p_scene_parent, p_node_index); + } } - + // Add the node we generated and set the owner to the scene root. p_scene_parent->add_child(current_node); if (current_node != p_scene_root) { - current_node->set_owner(p_scene_root); + 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_name(gltf_node->get_name()); @@ -5655,19 +5701,33 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref p_state, Node *p_ // and attach it to the bone_attachment p_scene_parent = bone_attachment; } - - // We still have not managed to make a node - if (gltf_node->mesh >= 0) { - current_node = _generate_mesh_instance(p_state, p_scene_parent, p_node_index); - } else if (gltf_node->camera >= 0) { - current_node = _generate_camera(p_state, p_scene_parent, p_node_index); - } else if (gltf_node->light >= 0) { - current_node = _generate_light(p_state, p_scene_parent, p_node_index); + // Check if any GLTFDocumentExtension classes want to generate a node for us. + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + current_node = ext->generate_scene_node(p_state, gltf_node, p_scene_parent); + if (current_node) { + break; + } } - + // If none of our GLTFDocumentExtension classes generated us a node, we generate one. + if (!current_node) { + if (gltf_node->mesh >= 0) { + current_node = _generate_mesh_instance(p_state, p_scene_parent, p_node_index); + } else if (gltf_node->camera >= 0) { + current_node = _generate_camera(p_state, p_scene_parent, p_node_index); + } else if (gltf_node->light >= 0) { + current_node = _generate_light(p_state, p_scene_parent, p_node_index); + } else { + current_node = _generate_spatial(p_state, p_scene_parent, p_node_index); + } + } + // Add the node we generated and set the owner to the scene root. p_scene_parent->add_child(current_node); if (current_node != p_scene_root) { - current_node->set_owner(p_scene_root); + Array args; + args.append(p_scene_root); + current_node->propagate_call(StringName("set_owner"), args); } // Do not set transform here. Transform is already applied to our bone. if (p_state->use_legacy_names) { @@ -6520,6 +6580,83 @@ void GLTFDocument::_convert_animation(Ref p_state, AnimationPlayer *p } } +void GLTFDocument::_bind_methods() { + ClassDB::bind_method(D_METHOD("register_gltf_document_extension", "extension", "first_priority"), &GLTFDocument::_register_gltf_document_extension, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("unregister_gltf_document_extension", "extension"), &GLTFDocument::_unregister_gltf_document_extension); + ClassDB::bind_method(D_METHOD("unregister_all_gltf_document_extensions"), &GLTFDocument::_unregister_all_gltf_document_extensions); +} + +// Since Godot 3.x does not have static methods, we'll use instance methods to call the static methods. +void GLTFDocument::_register_gltf_document_extension(Ref p_extension, bool p_first_priority) { + GLTFDocument::register_gltf_document_extension(p_extension, p_first_priority); +} + +void GLTFDocument::_unregister_gltf_document_extension(Ref p_extension) { + GLTFDocument::unregister_gltf_document_extension(p_extension); +} + +void GLTFDocument::_unregister_all_gltf_document_extensions() { + GLTFDocument::unregister_all_gltf_document_extensions(); +} + +Vector> GLTFDocument::all_document_extensions; + +void GLTFDocument::register_gltf_document_extension(Ref p_extension, bool p_first_priority) { + if (all_document_extensions.find(p_extension) == -1) { + if (p_first_priority) { + all_document_extensions.insert(0, p_extension); + } else { + all_document_extensions.push_back(p_extension); + } + } +} + +void GLTFDocument::unregister_gltf_document_extension(Ref p_extension) { + all_document_extensions.erase(p_extension); +} + +void GLTFDocument::unregister_all_gltf_document_extensions() { + all_document_extensions.clear(); +} + +void GLTFDocument::extension_generate_scene(Ref p_state) { + ERR_FAIL_COND(p_state.is_null()); + ERR_FAIL_INDEX(0, p_state->root_nodes.size()); + Error err = OK; + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + err = ext->import_post_parse(p_state); + ERR_FAIL_COND(err != OK); + } + GLTFNodeIndex gltf_root = p_state->root_nodes.write[0]; + Node *gltf_root_node = p_state->get_scene_node(gltf_root); + Node *root = gltf_root_node->get_parent(); + ERR_FAIL_NULL(root); + for (int node_i = 0; node_i < p_state->scene_nodes.size(); node_i++) { + Node *node = p_state->scene_nodes[node_i]; + ERR_CONTINUE(!node); + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + ERR_CONTINUE(!p_state->json.has("nodes")); + Array nodes = p_state->json["nodes"]; + ERR_CONTINUE(node_i >= nodes.size()); + ERR_CONTINUE(node_i < 0); + Dictionary node_json = nodes[node_i]; + Ref gltf_node = p_state->nodes[node_i]; + err = ext->import_node(p_state, gltf_node, node_json, node); + ERR_CONTINUE(err != OK); + } + } + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + err = ext->import_post(p_state, root); + ERR_CONTINUE(err != OK); + } +} + Error GLTFDocument::parse(Ref p_state, String p_path, bool p_read_binary) { Error err; FileAccessRef file = FileAccess::open(p_path, FileAccess::READ, &err); @@ -6557,6 +6694,16 @@ Error GLTFDocument::parse(Ref p_state, String p_path, bool p_read_bin p_state->major_version = version.get_slice(".", 0).to_int(); p_state->minor_version = version.get_slice(".", 1).to_int(); + document_extensions.clear(); + for (int ext_i = 0; ext_i < all_document_extensions.size(); ext_i++) { + Ref ext = all_document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + err = ext->import_preflight(p_state, p_state->json["extensionsUsed"]); + if (err == OK) { + document_extensions.push_back(ext); + } + } + /* PARSE EXTENSIONS */ err = _parse_gltf_extensions(p_state); @@ -6791,13 +6938,32 @@ Error GLTFDocument::_serialize_file(Ref p_state, const String p_path) } Error GLTFDocument::_parse_gltf_extensions(Ref p_state) { - ERR_FAIL_COND_V(!p_state.is_valid(), ERR_PARSE_ERROR); - if (p_state->json.has("extensionsRequired") && p_state->json["extensionsRequired"].get_type() == Variant::ARRAY) { - Array extensions_required = p_state->json["extensionsRequired"]; - if (extensions_required.find("KHR_draco_mesh_compression") != -1) { - ERR_PRINT("glTF2 extension KHR_draco_mesh_compression is not supported."); - return ERR_UNAVAILABLE; + ERR_FAIL_COND_V(p_state.is_null(), ERR_PARSE_ERROR); + if (p_state->json.has("extensionsUsed")) { + Vector ext_array = p_state->json["extensionsUsed"]; + p_state->extensions_used = ext_array; + } + if (p_state->json.has("extensionsRequired")) { + Vector ext_array = p_state->json["extensionsRequired"]; + p_state->extensions_required = ext_array; + } + Set supported_extensions; + supported_extensions.insert("KHR_lights_punctual"); + supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); + supported_extensions.insert("KHR_texture_transform"); + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref ext = document_extensions[ext_i]; + Vector ext_supported_extensions = ext->get_supported_extensions(); + for (int i = 0; i < ext_supported_extensions.size(); ++i) { + supported_extensions.insert(ext_supported_extensions[i]); } } - return OK; + Error ret = Error::OK; + for (int i = 0; i < p_state->extensions_required.size(); i++) { + if (!supported_extensions.has(p_state->extensions_required[i])) { + ERR_PRINT("GLTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); + ret = ERR_UNAVAILABLE; + } + } + return ret; } diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index 049f0327730..674c067e729 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -31,6 +31,7 @@ #ifndef GLTF_DOCUMENT_H #define GLTF_DOCUMENT_H +#include "extensions/gltf_document_extension.h" #include "gltf_defines.h" #include "structures/gltf_animation.h" @@ -55,6 +56,8 @@ class GridMap; class GLTFDocument : public Resource { GDCLASS(GLTFDocument, Resource); + static Vector> all_document_extensions; + Vector> document_extensions; private: const float BAKE_FPS = 30.0f; @@ -81,6 +84,17 @@ public: COMPONENT_TYPE_FLOAT = 5126, }; +protected: + static void _bind_methods(); + +public: + void _register_gltf_document_extension(Ref p_extension, bool p_first_priority = false); + void _unregister_gltf_document_extension(Ref p_extension); + void _unregister_all_gltf_document_extensions(); + static void register_gltf_document_extension(Ref p_extension, bool p_first_priority = false); + static void unregister_gltf_document_extension(Ref p_extension); + static void unregister_all_gltf_document_extensions(); + private: double _filter_number(double p_float); String _get_component_type_name(const uint32_t p_component); @@ -273,7 +287,7 @@ private: Dictionary _serialize_texture_transform_uv2(Ref p_material); Error _serialize_version(Ref p_state); Error _serialize_file(Ref p_state, const String p_path); - Error _serialize_extensions(Ref p_state) const; + Error _serialize_gltf_extensions(Ref p_state) const; public: // http://www.itu.int/rec/R-REC-BT.601 @@ -292,6 +306,8 @@ private: static float get_max_component(const Color &p_color); public: + void extension_generate_scene(Ref p_state); + String _sanitize_scene_name(Ref p_state, const String &p_name); String _legacy_validate_node_name(const String &p_name); diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index bf4453fadf0..4f8d13830de 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -33,6 +33,7 @@ #include "scene/animation/animation_player.h" void GLTFState::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_used_extension", "extension_name", "required"), &GLTFState::add_used_extension); ClassDB::bind_method(D_METHOD("get_json"), &GLTFState::get_json); ClassDB::bind_method(D_METHOD("set_json", "json"), &GLTFState::set_json); ClassDB::bind_method(D_METHOD("get_major_version"), &GLTFState::get_major_version); @@ -86,6 +87,8 @@ void GLTFState::_bind_methods() { ClassDB::bind_method(D_METHOD("get_animations"), &GLTFState::get_animations); ClassDB::bind_method(D_METHOD("set_animations", "animations"), &GLTFState::set_animations); ClassDB::bind_method(D_METHOD("get_scene_node", "idx"), &GLTFState::get_scene_node); + ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFState::get_additional_data); + ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFState::set_additional_data); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "json"), "set_json", "get_json"); // Dictionary ADD_PROPERTY(PropertyInfo(Variant::INT, "major_version"), "set_major_version", "get_major_version"); // int @@ -114,6 +117,17 @@ void GLTFState::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector> } +void GLTFState::add_used_extension(const String &p_extension_name, bool p_required) { + if (extensions_used.find(p_extension_name) == -1) { + extensions_used.push_back(p_extension_name); + } + if (p_required) { + if (extensions_required.find(p_extension_name) == -1) { + extensions_required.push_back(p_extension_name); + } + } +} + Dictionary GLTFState::get_json() { return json; } @@ -329,3 +343,11 @@ AnimationPlayer *GLTFState::get_animation_player(int idx) { ERR_FAIL_INDEX_V(idx, animation_players.size(), nullptr); return animation_players[idx]; } + +Variant GLTFState::get_additional_data(const String &p_extension_name) { + return additional_data[p_extension_name]; +} + +void GLTFState::set_additional_data(const String &p_extension_name, Variant p_additional_data) { + additional_data[p_extension_name] = p_additional_data; +} diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index cd1ba2bf428..9599a67849d 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -79,6 +79,8 @@ class GLTFState : public Resource { Ref default_texture_sampler; Vector> images; Map> texture_cache; + Vector extensions_used; + Vector extensions_required; Vector> skins; Vector> cameras; @@ -93,11 +95,14 @@ class GLTFState : public Resource { Map skeleton3d_to_gltf_skeleton; Map> skin_and_skeleton3d_to_gltf_skin; + Dictionary additional_data; protected: static void _bind_methods(); public: + void add_used_extension(const String &p_extension, bool p_required = false); + Dictionary get_json(); void set_json(Dictionary p_json); @@ -178,6 +183,9 @@ public: int get_animation_players_count(int idx); AnimationPlayer *get_animation_player(int idx); + + Variant get_additional_data(const String &p_extension_name); + void set_additional_data(const String &p_extension_name, Variant p_additional_data); }; #endif // GLTF_STATE_H diff --git a/modules/gltf/packed_scene_gltf.cpp b/modules/gltf/packed_scene_gltf.cpp index a7f94b114ce..5d4922c16ad 100644 --- a/modules/gltf/packed_scene_gltf.cpp +++ b/modules/gltf/packed_scene_gltf.cpp @@ -93,6 +93,7 @@ Node *PackedSceneGLTF::import_scene(const String &p_path, uint32_t p_flags, gltf_document->_import_animation(r_state, ap, i, p_bake_fps); } } + gltf_document->extension_generate_scene(r_state); return cast_to(root); } diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 57556ed9890..f0ec66019ea 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -32,6 +32,7 @@ #include "register_types.h" +#include "extensions/gltf_document_extension.h" #include "extensions/gltf_spec_gloss.h" #include "gltf_state.h" @@ -73,6 +74,7 @@ void register_gltf_types() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); } diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp index 5324acb6e50..b4142729175 100644 --- a/modules/gltf/structures/gltf_node.cpp +++ b/modules/gltf/structures/gltf_node.cpp @@ -57,6 +57,8 @@ void GLTFNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children); 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_additional_data", "extension_name"), &GLTFNode::get_additional_data); + ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data); ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height"); // int @@ -176,3 +178,11 @@ GLTFLightIndex GLTFNode::get_light() { void GLTFNode::set_light(GLTFLightIndex p_light) { light = p_light; } + +Variant GLTFNode::get_additional_data(const String &p_extension_name) { + return additional_data[p_extension_name]; +} + +void GLTFNode::set_additional_data(const String &p_extension_name, Variant p_additional_data) { + additional_data[p_extension_name] = p_additional_data; +} diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h index 63593d1ec40..7f04eeafc6c 100644 --- a/modules/gltf/structures/gltf_node.h +++ b/modules/gltf/structures/gltf_node.h @@ -54,6 +54,7 @@ private: Vector3 scale = Vector3(1, 1, 1); Vector children; GLTFLightIndex light = -1; + Dictionary additional_data; protected: static void _bind_methods(); @@ -97,6 +98,9 @@ public: GLTFLightIndex get_light(); void set_light(GLTFLightIndex p_light); + + Variant get_additional_data(const String &p_extension_name); + void set_additional_data(const String &p_extension_name, Variant p_additional_data); }; #endif // GLTF_NODE_H