Merge pull request #70411 from aaronfranke/3.x-gltf-document-extension

[3.x] Backport the GLTFDocumentExtension system
This commit is contained in:
Rémi Verschelde 2022-12-22 08:56:48 +01:00
commit 3571b3f194
No known key found for this signature in database
GPG key ID: C3336907360768E1
16 changed files with 680 additions and 33 deletions

View file

@ -14,6 +14,7 @@ def get_doc_classes():
"GLTFBufferView",
"GLTFCamera",
"GLTFDocument",
"GLTFDocumentExtension",
"GLTFLight",
"GLTFMesh",
"GLTFNode",

View file

@ -8,6 +8,28 @@
<tutorials>
</tutorials>
<methods>
<method name="register_gltf_document_extension">
<return type="void" />
<argument index="0" name="extension" type="GLTFDocumentExtension" />
<argument index="1" name="first_priority" type="bool" default="false" />
<description>
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].
</description>
</method>
<method name="unregister_all_gltf_document_extensions">
<return type="void" />
<description>
Unregisters all [GLTFDocumentExtension] instances.
</description>
</method>
<method name="unregister_gltf_document_extension">
<return type="void" />
<argument index="0" name="extension" type="GLTFDocumentExtension" />
<description>
Unregisters the given [GLTFDocumentExtension] instance.
</description>
</method>
</methods>
<constants>
</constants>

View file

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFDocumentExtension" inherits="Resource" version="3.6" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
[GLTFDocument] extension class.
</brief_description>
<description>
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].
</description>
<tutorials>
</tutorials>
<methods>
<method name="_convert_scene_node" qualifiers="virtual">
<return type="void" />
<argument index="0" name="state" type="Object" />
<argument index="1" name="gltf_node" type="Object" />
<argument index="2" name="scene_node" type="Object" />
<description>
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].
</description>
</method>
<method name="_export_node" qualifiers="virtual">
<return type="int" />
<argument index="0" name="state" type="Object" />
<argument index="1" name="gltf_node" type="Object" />
<argument index="2" name="json" type="Dictionary" />
<argument index="3" name="node" type="Object" />
<description>
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.
</description>
</method>
<method name="_export_post" qualifiers="virtual">
<return type="int" />
<argument index="0" name="state" type="Object" />
<description>
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.
</description>
</method>
<method name="_export_preflight" qualifiers="virtual">
<return type="int" />
<argument index="0" name="state" type="Object" />
<argument index="1" name="root" type="Object" />
<description>
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.
</description>
</method>
<method name="_generate_scene_node" qualifiers="virtual">
<return type="Object" />
<argument index="0" name="state" type="Object" />
<argument index="1" name="gltf_node" type="Object" />
<argument index="2" name="scene_parent" type="Object" />
<description>
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.
</description>
</method>
<method name="_get_supported_extensions" qualifiers="virtual">
<return type="Array" />
<description>
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.
</description>
</method>
<method name="_import_node" qualifiers="virtual">
<return type="int" />
<argument index="0" name="state" type="Object" />
<argument index="1" name="gltf_node" type="Object" />
<argument index="2" name="json" type="Dictionary" />
<argument index="3" name="node" type="Object" />
<description>
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.
</description>
</method>
<method name="_import_post" qualifiers="virtual">
<return type="int" />
<argument index="0" name="state" type="Object" />
<argument index="1" name="root" type="Object" />
<description>
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.
</description>
</method>
<method name="_import_post_parse" qualifiers="virtual">
<return type="int" />
<argument index="0" name="state" type="Object" />
<description>
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.
</description>
</method>
<method name="_import_preflight" qualifiers="virtual">
<return type="int" />
<argument index="0" name="state" type="Object" />
<argument index="1" name="extensions" type="PoolStringArray" />
<description>
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.
</description>
</method>
<method name="_parse_node_extensions" qualifiers="virtual">
<return type="int" />
<argument index="0" name="state" type="Object" />
<argument index="1" name="gltf_node" type="Object" />
<argument index="2" name="extensions" type="Dictionary" />
<description>
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.
</description>
</method>
</methods>
<constants>
</constants>
</class>

View file

@ -1,13 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFNode" inherits="Resource" version="3.6" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
GLTF node class.
</brief_description>
<description>
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.
</description>
<tutorials>
<link title="GLTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link>
</tutorials>
<methods>
<method name="get_additional_data">
<return type="Variant" />
<argument index="0" name="extension_name" type="String" />
<description>
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.
</description>
</method>
<method name="set_additional_data">
<return type="void" />
<argument index="0" name="extension_name" type="String" />
<argument index="1" name="additional_data" type="Variant" />
<description>
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.
</description>
</method>
</methods>
<members>
<member name="camera" type="int" setter="set_camera" getter="get_camera" default="-1">

View file

@ -8,11 +8,27 @@
<tutorials>
</tutorials>
<methods>
<method name="add_used_extension">
<return type="void" />
<argument index="0" name="extension_name" type="String" />
<argument index="1" name="required" type="bool" />
<description>
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.
</description>
</method>
<method name="get_accessors">
<return type="Array" />
<description>
</description>
</method>
<method name="get_additional_data">
<return type="Variant" />
<argument index="0" name="extension_name" type="String" />
<description>
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.
</description>
</method>
<method name="get_animation_player">
<return type="AnimationPlayer" />
<argument index="0" name="idx" type="int" />
@ -113,6 +129,15 @@
<description>
</description>
</method>
<method name="set_additional_data">
<return type="void" />
<argument index="0" name="extension_name" type="String" />
<argument index="1" name="additional_data" type="Variant" />
<description>
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.
</description>
</method>
<method name="set_animations">
<return type="void" />
<argument index="0" name="animations" type="Array" />

View file

@ -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<GLTFState> p_state, Vector<String> 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<String> GLTFDocumentExtension::get_supported_extensions() {
Vector<String> ret;
ScriptInstance *si = get_script_instance();
if (!si) {
return ret;
}
si->call("_get_supported_extensions");
return ret;
}
Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> 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<GLTFState> p_state, Ref<GLTFNode> 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<Spatial>(ret.operator Object *());
return ret_node;
}
Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> 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<GLTFState> p_state, Ref<GLTFNode> 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<GLTFState> 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<GLTFState> 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<GLTFState> p_state, Ref<GLTFNode> 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<GLTFState> p_state, Ref<GLTFNode> 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<GLTFState> 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);
}

View file

@ -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<GLTFState> p_state, Vector<String> p_extensions);
virtual Vector<String> get_supported_extensions();
virtual Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions);
virtual Spatial *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
virtual Error import_post_parse(Ref<GLTFState> p_state);
virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node);
virtual Error import_post(Ref<GLTFState> p_state, Node *p_node);
// Export process.
virtual Error export_preflight(Ref<GLTFState> p_state, Node *p_root);
virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node);
virtual Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node);
virtual Error export_post(Ref<GLTFState> p_state);
};
#endif // GLTF_DOCUMENT_EXTENSION_H

View file

@ -50,6 +50,7 @@ class GLTFAnimation;
class GLTFBufferView;
class GLTFCamera;
class GLTFDocument;
class GLTFDocumentExtension;
class GLTFLight;
class GLTFMesh;
class GLTFNode;

View file

@ -90,6 +90,15 @@ Error GLTFDocument::serialize(Ref<GLTFState> 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<GLTFDocumentExtension> 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<uint8_t>());
@ -175,7 +184,7 @@ Error GLTFDocument::serialize(Ref<GLTFState> 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<GLTFState> 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<GLTFDocumentExtension> 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<GLTFState> p_state, Node *p_root, const String
return OK;
}
Error GLTFDocument::_serialize_extensions(Ref<GLTFState> p_state) const {
Array extensions_used;
Array extensions_required;
Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> p_state) const {
Vector<String> extensions_used = p_state->extensions_used;
Vector<String> 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<GLTFState> 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<GLTFState> p_state) {
}
node["children"] = children;
}
for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
Ref<GLTFDocumentExtension> 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<GLTFState> p_state) {
node->light = light;
}
}
for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
Ref<GLTFDocumentExtension> 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<GLTFState> p_state, Node *p_current,
AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(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<GLTFDocumentExtension> 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<GLTFState> 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<GLTFDocumentExtension> 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<GLTFState> 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<GLTFDocumentExtension> 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<GLTFState> 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<GLTFDocumentExtension> p_extension, bool p_first_priority) {
GLTFDocument::register_gltf_document_extension(p_extension, p_first_priority);
}
void GLTFDocument::_unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension) {
GLTFDocument::unregister_gltf_document_extension(p_extension);
}
void GLTFDocument::_unregister_all_gltf_document_extensions() {
GLTFDocument::unregister_all_gltf_document_extensions();
}
Vector<Ref<GLTFDocumentExtension>> GLTFDocument::all_document_extensions;
void GLTFDocument::register_gltf_document_extension(Ref<GLTFDocumentExtension> 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<GLTFDocumentExtension> 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<GLTFState> 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<GLTFDocumentExtension> 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<GLTFDocumentExtension> 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<GLTFNode> 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<GLTFDocumentExtension> 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<GLTFState> 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<GLTFState> 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<GLTFDocumentExtension> 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<GLTFState> p_state, const String p_path)
}
Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> 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<String> ext_array = p_state->json["extensionsUsed"];
p_state->extensions_used = ext_array;
}
if (p_state->json.has("extensionsRequired")) {
Vector<String> ext_array = p_state->json["extensionsRequired"];
p_state->extensions_required = ext_array;
}
Set<String> 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<GLTFDocumentExtension> ext = document_extensions[ext_i];
Vector<String> 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;
}

View file

@ -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<Ref<GLTFDocumentExtension>> all_document_extensions;
Vector<Ref<GLTFDocumentExtension>> 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<GLTFDocumentExtension> p_extension, bool p_first_priority = false);
void _unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension);
void _unregister_all_gltf_document_extensions();
static void register_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension, bool p_first_priority = false);
static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> 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<SpatialMaterial> p_material);
Error _serialize_version(Ref<GLTFState> p_state);
Error _serialize_file(Ref<GLTFState> p_state, const String p_path);
Error _serialize_extensions(Ref<GLTFState> p_state) const;
Error _serialize_gltf_extensions(Ref<GLTFState> 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<GLTFState> p_state);
String _sanitize_scene_name(Ref<GLTFState> p_state, const String &p_name);
String _legacy_validate_node_name(const String &p_name);

View file

@ -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<Ref<GLTFAnimation>>
}
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;
}

View file

@ -79,6 +79,8 @@ class GLTFState : public Resource {
Ref<GLTFTextureSampler> default_texture_sampler;
Vector<Ref<Image>> images;
Map<GLTFTextureIndex, Ref<Texture>> texture_cache;
Vector<String> extensions_used;
Vector<String> extensions_required;
Vector<Ref<GLTFSkin>> skins;
Vector<Ref<GLTFCamera>> cameras;
@ -93,11 +95,14 @@ class GLTFState : public Resource {
Map<ObjectID, GLTFSkeletonIndex> skeleton3d_to_gltf_skeleton;
Map<ObjectID, Map<ObjectID, GLTFSkinIndex>> 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

View file

@ -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<Spatial>(root);
}

View file

@ -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<GLTFLight>();
ClassDB::register_class<GLTFState>();
ClassDB::register_class<GLTFDocument>();
ClassDB::register_class<GLTFDocumentExtension>();
ClassDB::register_class<PackedSceneGLTF>();
}

View file

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

View file

@ -54,6 +54,7 @@ private:
Vector3 scale = Vector3(1, 1, 1);
Vector<int> 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