From 848d31bfdb27cf0f1bff6dd4e56fb11333bd16ad Mon Sep 17 00:00:00 2001 From: Relintai Date: Thu, 22 Dec 2022 19:24:45 +0100 Subject: [PATCH] Ported: [3.x] Backport the GLTFDocumentExtension system - aaronfranke https://github.com/godotengine/godot/commit/62e10daf7039da28c6fd8c8256b1fe91544156ee --- editor_modules/gltf/config.py | 1 + .../gltf/doc_classes/GLTFDocument.xml | 22 ++ .../doc_classes/GLTFDocumentExtension.xml | 118 ++++++++ editor_modules/gltf/doc_classes/GLTFNode.xml | 20 ++ editor_modules/gltf/doc_classes/GLTFState.xml | 25 ++ .../extensions/gltf_document_extension.cpp | 172 +++++++++++ .../gltf/extensions/gltf_document_extension.h | 62 ++++ editor_modules/gltf/gltf_defines.h | 1 + editor_modules/gltf/gltf_document.cpp | 281 +++++++++++++++--- editor_modules/gltf/gltf_document.h | 19 +- editor_modules/gltf/gltf_state.cpp | 22 ++ editor_modules/gltf/gltf_state.h | 10 +- editor_modules/gltf/packed_scene_gltf.cpp | 2 + editor_modules/gltf/register_types.cpp | 2 + editor_modules/gltf/structures/gltf_node.cpp | 10 + editor_modules/gltf/structures/gltf_node.h | 4 + 16 files changed, 722 insertions(+), 49 deletions(-) create mode 100644 editor_modules/gltf/doc_classes/GLTFDocumentExtension.xml create mode 100644 editor_modules/gltf/extensions/gltf_document_extension.cpp create mode 100644 editor_modules/gltf/extensions/gltf_document_extension.h diff --git a/editor_modules/gltf/config.py b/editor_modules/gltf/config.py index 41b76769b..c98e12be4 100644 --- a/editor_modules/gltf/config.py +++ b/editor_modules/gltf/config.py @@ -14,6 +14,7 @@ def get_doc_classes(): "GLTFBufferView", "GLTFCamera", "GLTFDocument", + "GLTFDocumentExtension", "GLTFLight", "GLTFMesh", "GLTFNode", diff --git a/editor_modules/gltf/doc_classes/GLTFDocument.xml b/editor_modules/gltf/doc_classes/GLTFDocument.xml index 779e1c9fb..fe4ed264b 100644 --- a/editor_modules/gltf/doc_classes/GLTFDocument.xml +++ b/editor_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/editor_modules/gltf/doc_classes/GLTFDocumentExtension.xml b/editor_modules/gltf/doc_classes/GLTFDocumentExtension.xml new file mode 100644 index 000000000..457257d3d --- /dev/null +++ b/editor_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/editor_modules/gltf/doc_classes/GLTFNode.xml b/editor_modules/gltf/doc_classes/GLTFNode.xml index c7e0b1b71..3e1a83569 100644 --- a/editor_modules/gltf/doc_classes/GLTFNode.xml +++ b/editor_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/editor_modules/gltf/doc_classes/GLTFState.xml b/editor_modules/gltf/doc_classes/GLTFState.xml index 2e40dde47..f4a001191 100644 --- a/editor_modules/gltf/doc_classes/GLTFState.xml +++ b/editor_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/editor_modules/gltf/extensions/gltf_document_extension.cpp b/editor_modules/gltf/extensions/gltf_document_extension.cpp new file mode 100644 index 000000000..87b655a75 --- /dev/null +++ b/editor_modules/gltf/extensions/gltf_document_extension.cpp @@ -0,0 +1,172 @@ +/*************************************************************************/ +/* 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" +#include "scene/3d/spatial.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/editor_modules/gltf/extensions/gltf_document_extension.h b/editor_modules/gltf/extensions/gltf_document_extension.h new file mode 100644 index 000000000..4d74638bb --- /dev/null +++ b/editor_modules/gltf/extensions/gltf_document_extension.h @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* 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 "core/object/resource.h" + +#include "../gltf_state.h" + +class Spatial; + +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/editor_modules/gltf/gltf_defines.h b/editor_modules/gltf/gltf_defines.h index aa48caa05..ae4d96ddf 100644 --- a/editor_modules/gltf/gltf_defines.h +++ b/editor_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/editor_modules/gltf/gltf_document.cpp b/editor_modules/gltf/gltf_document.cpp index c4189ab83..a2751f607 100644 --- a/editor_modules/gltf/gltf_document.cpp +++ b/editor_modules/gltf/gltf_document.cpp @@ -98,6 +98,16 @@ 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()); @@ -183,7 +193,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; } @@ -199,6 +209,14 @@ 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); @@ -207,9 +225,10 @@ 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"); } @@ -218,9 +237,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; @@ -434,6 +455,7 @@ Error GLTFDocument::_serialize_nodes(Ref p_state) { if (!n->translation.is_equal_approx(Vector3())) { node["translation"] = _vec3_to_arr(n->translation); } + if (n->children.size()) { Array children; for (int j = 0; j < n->children.size(); j++) { @@ -441,9 +463,20 @@ 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, n, node, p_state->scene_nodes[i]); + ERR_CONTINUE(err != OK); + } + nodes.push_back(node); } + p_state->json["nodes"] = nodes; + return OK; } @@ -655,6 +688,13 @@ 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")) { @@ -5314,6 +5354,13 @@ 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) { @@ -5581,22 +5628,34 @@ void GLTFDocument::_generate_scene_node(Ref p_state, Node *p_scene_pa } #endif - 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()); @@ -5609,8 +5668,8 @@ void GLTFDocument::_generate_scene_node(Ref p_state, Node *p_scene_pa } #ifdef MODULE_SKELETON_3D_ENABLED -void GLTFDocument::_generate_skeleton_bone_node(Ref p_state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) { - Ref gltf_node = p_state->nodes[node_index]; +void GLTFDocument::_generate_skeleton_bone_node(Ref p_state, Node *p_scene_parent, Spatial *p_scene_root, const GLTFNodeIndex p_node_index) { + Ref gltf_node = p_state->nodes[p_node_index]; Spatial *current_node = nullptr; @@ -5619,28 +5678,28 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref p_state, Node *sc const bool is_skinned_mesh = (gltf_node->skin >= 0 && gltf_node->mesh >= 0); const bool requires_extra_node = (gltf_node->mesh >= 0 || gltf_node->camera >= 0 || gltf_node->light >= 0); - Skeleton *active_skeleton = Object::cast_to(scene_parent); + Skeleton *active_skeleton = Object::cast_to(p_scene_parent); if (active_skeleton != skeleton) { if (active_skeleton) { // Bone Attachment - Direct Parented Skeleton Case - BoneAttachment *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, node_index, gltf_node->parent); + BoneAttachment *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, gltf_node->parent); - scene_parent->add_child(bone_attachment); - bone_attachment->set_owner(scene_root); + p_scene_parent->add_child(bone_attachment); + bone_attachment->set_owner(p_scene_root); // There is no gltf_node that represent this, so just directly create a unique name bone_attachment->set_name(_gen_unique_name(p_state, "BoneAttachment")); // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node // and attach it to the bone_attachment - scene_parent = bone_attachment; - WARN_PRINT(vformat("glTF: Generating scene detected direct parented Skeletons at node %d", node_index)); + p_scene_parent = bone_attachment; + WARN_PRINT(vformat("glTF: Generating scene detected direct parented Skeletons at node %d", p_node_index)); } // Add it to the scene if it has not already been added if (skeleton->get_parent() == nullptr) { - scene_parent->add_child(skeleton); - skeleton->set_owner(scene_root); + p_scene_parent->add_child(skeleton); + skeleton->set_owner(p_scene_root); } } @@ -5651,32 +5710,50 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref p_state, Node *sc // skinned meshes must not be placed in a bone attachment. if (!is_skinned_mesh) { // Bone Attachment - Same Node Case - BoneAttachment *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, node_index, node_index); + BoneAttachment *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, p_node_index); - scene_parent->add_child(bone_attachment); - bone_attachment->set_owner(scene_root); + p_scene_parent->add_child(bone_attachment); + bone_attachment->set_owner(p_scene_root); // There is no gltf_node that represent this, so just directly create a unique name bone_attachment->set_name(_gen_unique_name(p_state, "BoneAttachment")); // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node // and attach it to the bone_attachment - scene_parent = 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, scene_parent, node_index); - } else if (gltf_node->camera >= 0) { - current_node = _generate_camera(p_state, scene_parent, node_index); - } else if (gltf_node->light >= 0) { - current_node = _generate_light(p_state, scene_parent, 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; + } } - scene_parent->add_child(current_node); - if (current_node != scene_root) { - current_node->set_owner(scene_root); + // 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) { + 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) { current_node->set_name(_legacy_validate_node_name(gltf_node->get_name())); @@ -5685,10 +5762,10 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref p_state, Node *sc } } - p_state->scene_nodes.insert(node_index, current_node); + p_state->scene_nodes.insert(p_node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { - _generate_scene_node(p_state, active_skeleton, scene_root, gltf_node->children[i]); + _generate_scene_node(p_state, active_skeleton, p_scene_root, gltf_node->children[i]); } } #endif @@ -6584,6 +6661,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 f = FileAccess::open(p_path, FileAccess::READ, &err); @@ -6621,6 +6775,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); @@ -6857,13 +7021,36 @@ 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/editor_modules/gltf/gltf_document.h b/editor_modules/gltf/gltf_document.h index 855db686c..2a075f07e 100644 --- a/editor_modules/gltf/gltf_document.h +++ b/editor_modules/gltf/gltf_document.h @@ -38,6 +38,7 @@ #include "scene/resources/material.h" #include "scene/resources/texture.h" +#include "extensions/gltf_document_extension.h" #include "gltf_defines.h" #include "structures/gltf_animation.h" @@ -56,6 +57,9 @@ class GLTFDocument : public Resource { GDCLASS(GLTFDocument, Resource); private: + static Vector> all_document_extensions; + Vector> document_extensions; + const float BAKE_FPS = 30.0f; public: @@ -80,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); @@ -272,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 @@ -291,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/editor_modules/gltf/gltf_state.cpp b/editor_modules/gltf/gltf_state.cpp index bf4453fad..2ff317412 100644 --- a/editor_modules/gltf/gltf_state.cpp +++ b/editor_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; +} \ No newline at end of file diff --git a/editor_modules/gltf/gltf_state.h b/editor_modules/gltf/gltf_state.h index 4c326aeb3..cab177817 100644 --- a/editor_modules/gltf/gltf_state.h +++ b/editor_modules/gltf/gltf_state.h @@ -38,10 +38,10 @@ #include "structures/gltf_camera.h" #include "structures/gltf_mesh.h" #include "structures/gltf_node.h" +#include "structures/gltf_skeleton.h" #include "structures/gltf_skin.h" #include "structures/gltf_texture.h" #include "structures/gltf_texture_sampler.h" -#include "structures/gltf_skeleton.h" class GLTFState : public Resource { GDCLASS(GLTFState, Resource); @@ -78,6 +78,8 @@ class GLTFState : public Resource { Ref default_texture_sampler; Vector> images; Map> texture_cache; + Vector extensions_used; + Vector extensions_required; Vector> skins; Vector> cameras; @@ -92,11 +94,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); @@ -177,6 +182,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/editor_modules/gltf/packed_scene_gltf.cpp b/editor_modules/gltf/packed_scene_gltf.cpp index a7f94b114..a98547ac7 100644 --- a/editor_modules/gltf/packed_scene_gltf.cpp +++ b/editor_modules/gltf/packed_scene_gltf.cpp @@ -94,6 +94,8 @@ Node *PackedSceneGLTF::import_scene(const String &p_path, uint32_t p_flags, } } + gltf_document->extension_generate_scene(r_state); + return cast_to(root); } diff --git a/editor_modules/gltf/register_types.cpp b/editor_modules/gltf/register_types.cpp index 57556ed98..f0ec66019 100644 --- a/editor_modules/gltf/register_types.cpp +++ b/editor_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/editor_modules/gltf/structures/gltf_node.cpp b/editor_modules/gltf/structures/gltf_node.cpp index f221a71c9..f267c481f 100644 --- a/editor_modules/gltf/structures/gltf_node.cpp +++ b/editor_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; +} \ No newline at end of file diff --git a/editor_modules/gltf/structures/gltf_node.h b/editor_modules/gltf/structures/gltf_node.h index 9bbd9e4b4..aaaec6d6b 100644 --- a/editor_modules/gltf/structures/gltf_node.h +++ b/editor_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