From 6ec9468e75e88e03c2ea237194e0ddfda94bf24a Mon Sep 17 00:00:00 2001 From: "K. S. Ernest (iFire) Lee" Date: Tue, 2 Mar 2021 20:20:20 -0800 Subject: [PATCH 1/2] Backport gltf2 module from master. --- editor/editor_node.cpp | 5 - editor/import/editor_scene_importer_gltf.cpp | 3344 -------- editor/import/editor_scene_importer_gltf.h | 447 -- modules/gltf/SCsub | 10 + modules/gltf/config.py | 30 + .../doc_classes/EditorSceneImporterGLTF.xml | 13 + modules/gltf/doc_classes/GLTFAccessor.xml | 43 + modules/gltf/doc_classes/GLTFAnimation.xml | 17 + modules/gltf/doc_classes/GLTFBufferView.xml | 25 + modules/gltf/doc_classes/GLTFCamera.xml | 23 + modules/gltf/doc_classes/GLTFDocument.xml | 13 + modules/gltf/doc_classes/GLTFLight.xml | 27 + modules/gltf/doc_classes/GLTFMesh.xml | 19 + modules/gltf/doc_classes/GLTFNode.xml | 41 + modules/gltf/doc_classes/GLTFSkeleton.xml | 67 + modules/gltf/doc_classes/GLTFSkin.xml | 71 + modules/gltf/doc_classes/GLTFSpecGloss.xml | 25 + modules/gltf/doc_classes/GLTFState.xml | 265 + modules/gltf/doc_classes/GLTFTexture.xml | 17 + modules/gltf/doc_classes/PackedSceneGLTF.xml | 58 + .../editor_scene_exporter_gltf_plugin.cpp | 95 + .../gltf/editor_scene_exporter_gltf_plugin.h | 55 + modules/gltf/editor_scene_importer_gltf.cpp | 187 + modules/gltf/editor_scene_importer_gltf.h | 96 + modules/gltf/gltf_accessor.cpp | 189 + modules/gltf/gltf_accessor.h | 104 + modules/gltf/gltf_animation.cpp | 53 + modules/gltf/gltf_animation.h | 74 + modules/gltf/gltf_buffer_view.cpp | 90 + modules/gltf/gltf_buffer_view.h | 68 + modules/gltf/gltf_camera.cpp | 47 + modules/gltf/gltf_camera.h | 58 + modules/gltf/gltf_document.cpp | 6737 +++++++++++++++++ modules/gltf/gltf_document.h | 435 ++ modules/gltf/gltf_light.cpp | 101 + modules/gltf/gltf_light.h | 72 + modules/gltf/gltf_mesh.cpp | 57 + modules/gltf/gltf_mesh.h | 54 + modules/gltf/gltf_node.cpp | 189 + modules/gltf/gltf_node.h | 105 + modules/gltf/gltf_skeleton.cpp | 95 + modules/gltf/gltf_skeleton.h | 101 + modules/gltf/gltf_skin.cpp | 155 + modules/gltf/gltf_skin.h | 109 + modules/gltf/gltf_spec_gloss.cpp | 90 + modules/gltf/gltf_spec_gloss.h | 67 + modules/gltf/gltf_state.cpp | 307 + modules/gltf/gltf_state.h | 186 + modules/gltf/gltf_texture.cpp | 46 + modules/gltf/gltf_texture.h | 51 + modules/gltf/register_types.cpp | 88 + modules/gltf/register_types.h | 32 + 52 files changed, 10957 insertions(+), 3796 deletions(-) delete mode 100644 editor/import/editor_scene_importer_gltf.cpp delete mode 100644 editor/import/editor_scene_importer_gltf.h create mode 100644 modules/gltf/SCsub create mode 100644 modules/gltf/config.py create mode 100644 modules/gltf/doc_classes/EditorSceneImporterGLTF.xml create mode 100644 modules/gltf/doc_classes/GLTFAccessor.xml create mode 100644 modules/gltf/doc_classes/GLTFAnimation.xml create mode 100644 modules/gltf/doc_classes/GLTFBufferView.xml create mode 100644 modules/gltf/doc_classes/GLTFCamera.xml create mode 100644 modules/gltf/doc_classes/GLTFDocument.xml create mode 100644 modules/gltf/doc_classes/GLTFLight.xml create mode 100644 modules/gltf/doc_classes/GLTFMesh.xml create mode 100644 modules/gltf/doc_classes/GLTFNode.xml create mode 100644 modules/gltf/doc_classes/GLTFSkeleton.xml create mode 100644 modules/gltf/doc_classes/GLTFSkin.xml create mode 100644 modules/gltf/doc_classes/GLTFSpecGloss.xml create mode 100644 modules/gltf/doc_classes/GLTFState.xml create mode 100644 modules/gltf/doc_classes/GLTFTexture.xml create mode 100644 modules/gltf/doc_classes/PackedSceneGLTF.xml create mode 100644 modules/gltf/editor_scene_exporter_gltf_plugin.cpp create mode 100644 modules/gltf/editor_scene_exporter_gltf_plugin.h create mode 100644 modules/gltf/editor_scene_importer_gltf.cpp create mode 100644 modules/gltf/editor_scene_importer_gltf.h create mode 100644 modules/gltf/gltf_accessor.cpp create mode 100644 modules/gltf/gltf_accessor.h create mode 100644 modules/gltf/gltf_animation.cpp create mode 100644 modules/gltf/gltf_animation.h create mode 100644 modules/gltf/gltf_buffer_view.cpp create mode 100644 modules/gltf/gltf_buffer_view.h create mode 100644 modules/gltf/gltf_camera.cpp create mode 100644 modules/gltf/gltf_camera.h create mode 100644 modules/gltf/gltf_document.cpp create mode 100644 modules/gltf/gltf_document.h create mode 100644 modules/gltf/gltf_light.cpp create mode 100644 modules/gltf/gltf_light.h create mode 100644 modules/gltf/gltf_mesh.cpp create mode 100644 modules/gltf/gltf_mesh.h create mode 100644 modules/gltf/gltf_node.cpp create mode 100644 modules/gltf/gltf_node.h create mode 100644 modules/gltf/gltf_skeleton.cpp create mode 100644 modules/gltf/gltf_skeleton.h create mode 100644 modules/gltf/gltf_skin.cpp create mode 100644 modules/gltf/gltf_skin.h create mode 100644 modules/gltf/gltf_spec_gloss.cpp create mode 100644 modules/gltf/gltf_spec_gloss.h create mode 100644 modules/gltf/gltf_state.cpp create mode 100644 modules/gltf/gltf_state.h create mode 100644 modules/gltf/gltf_texture.cpp create mode 100644 modules/gltf/gltf_texture.h create mode 100644 modules/gltf/register_types.cpp create mode 100644 modules/gltf/register_types.h diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 43247cb0f9..ef04cafadf 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -90,7 +90,6 @@ #include "editor/fileserver/editor_file_server.h" #include "editor/filesystem_dock.h" #include "editor/import/editor_import_collada.h" -#include "editor/import/editor_scene_importer_gltf.h" #include "editor/import/resource_importer_bitmask.h" #include "editor/import/resource_importer_csv_translation.h" #include "editor/import/resource_importer_image.h" @@ -5809,10 +5808,6 @@ EditorNode::EditorNode() { import_obj2.instance(); import_scene->add_importer(import_obj2); - Ref import_gltf; - import_gltf.instance(); - import_scene->add_importer(import_gltf); - Ref import_escn; import_escn.instance(); import_scene->add_importer(import_escn); diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp deleted file mode 100644 index 404875e7c7..0000000000 --- a/editor/import/editor_scene_importer_gltf.cpp +++ /dev/null @@ -1,3344 +0,0 @@ -/*************************************************************************/ -/* editor_scene_importer_gltf.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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 "editor_scene_importer_gltf.h" -#include "core/crypto/crypto_core.h" -#include "core/io/json.h" -#include "core/math/disjoint_set.h" -#include "core/math/math_defs.h" -#include "core/os/file_access.h" -#include "core/os/os.h" -#include "modules/regex/regex.h" -#include "scene/3d/bone_attachment.h" -#include "scene/3d/camera.h" -#include "scene/3d/mesh_instance.h" -#include "scene/animation/animation_player.h" -#include "scene/main/node.h" -#include "scene/resources/surface_tool.h" - -uint32_t EditorSceneImporterGLTF::get_import_flags() const { - return IMPORT_SCENE | IMPORT_ANIMATION; -} -void EditorSceneImporterGLTF::get_extensions(List *r_extensions) const { - r_extensions->push_back("gltf"); - r_extensions->push_back("glb"); -} - -Error EditorSceneImporterGLTF::_parse_json(const String &p_path, GLTFState &state) { - Error err; - FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); - if (!f) { - return err; - } - - Vector array; - array.resize(f->get_len()); - f->get_buffer(array.ptrw(), array.size()); - String text; - text.parse_utf8((const char *)array.ptr(), array.size()); - - String err_txt; - int err_line; - Variant v; - err = JSON::parse(text, v, err_txt, err_line); - if (err != OK) { - _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); - return err; - } - state.json = v; - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_glb(const String &p_path, GLTFState &state) { - Error err; - FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); - if (!f) { - return err; - } - - uint32_t magic = f->get_32(); - ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF - f->get_32(); // version - f->get_32(); // length - - uint32_t chunk_length = f->get_32(); - uint32_t chunk_type = f->get_32(); - - ERR_FAIL_COND_V(chunk_type != 0x4E4F534A, ERR_PARSE_ERROR); //JSON - Vector json_data; - json_data.resize(chunk_length); - uint32_t len = f->get_buffer(json_data.ptrw(), chunk_length); - ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); - - String text; - text.parse_utf8((const char *)json_data.ptr(), json_data.size()); - - String err_txt; - int err_line; - Variant v; - err = JSON::parse(text, v, err_txt, err_line); - if (err != OK) { - _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); - return err; - } - - state.json = v; - - //data? - - chunk_length = f->get_32(); - chunk_type = f->get_32(); - - if (f->eof_reached()) { - return OK; //all good - } - - ERR_FAIL_COND_V(chunk_type != 0x004E4942, ERR_PARSE_ERROR); //BIN - - state.glb_data.resize(chunk_length); - len = f->get_buffer(state.glb_data.ptrw(), chunk_length); - ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); - - return OK; -} - -static Vector3 _arr_to_vec3(const Array &p_array) { - ERR_FAIL_COND_V(p_array.size() != 3, Vector3()); - return Vector3(p_array[0], p_array[1], p_array[2]); -} - -static Quat _arr_to_quat(const Array &p_array) { - ERR_FAIL_COND_V(p_array.size() != 4, Quat()); - return Quat(p_array[0], p_array[1], p_array[2], p_array[3]); -} - -static Transform _arr_to_xform(const Array &p_array) { - ERR_FAIL_COND_V(p_array.size() != 16, Transform()); - - Transform xform; - xform.basis.set_axis(Vector3::AXIS_X, Vector3(p_array[0], p_array[1], p_array[2])); - xform.basis.set_axis(Vector3::AXIS_Y, Vector3(p_array[4], p_array[5], p_array[6])); - xform.basis.set_axis(Vector3::AXIS_Z, Vector3(p_array[8], p_array[9], p_array[10])); - xform.set_origin(Vector3(p_array[12], p_array[13], p_array[14])); - - return xform; -} - -String EditorSceneImporterGLTF::_sanitize_scene_name(GLTFState &state, const String &p_name) { - if (state.use_legacy_names) { - RegEx regex("([^a-zA-Z0-9_ -]+)"); - String s_name = regex.sub(p_name, "", true); - return s_name; - } else { - return p_name.validate_node_name(); - } -} - -String EditorSceneImporterGLTF::_legacy_validate_node_name(const String &p_name) { - String invalid_character = ". : @ / \""; - String name = p_name; - Vector chars = invalid_character.split(" "); - for (int i = 0; i < chars.size(); i++) { - name = name.replace(chars[i], ""); - } - return name; -} - -String EditorSceneImporterGLTF::_gen_unique_name(GLTFState &state, const String &p_name) { - const String s_name = _sanitize_scene_name(state, p_name); - - String name; - int index = 1; - while (true) { - name = s_name; - - if (index > 1) { - if (state.use_legacy_names) { - name += " "; - } - name += itos(index); - } - if (!state.unique_names.has(name)) { - break; - } - index++; - } - - state.unique_names.insert(name); - - return name; -} - -String EditorSceneImporterGLTF::_sanitize_animation_name(const String &p_name) { - // Animations disallow the normal node invalid characters as well as "," and "[" - // (See animation/animation_player.cpp::add_animation) - - // TODO: Consider adding invalid_characters or a _validate_animation_name to animation_player to mirror Node. - String name = p_name.validate_node_name(); - name = name.replace(",", ""); - name = name.replace("[", ""); - return name; -} - -String EditorSceneImporterGLTF::_gen_unique_animation_name(GLTFState &state, const String &p_name) { - const String s_name = _sanitize_animation_name(p_name); - - String name; - int index = 1; - while (true) { - name = s_name; - - if (index > 1) { - name += itos(index); - } - if (!state.unique_animation_names.has(name)) { - break; - } - index++; - } - - state.unique_animation_names.insert(name); - - return name; -} - -String EditorSceneImporterGLTF::_sanitize_bone_name(GLTFState &state, const String &p_name) { - if (state.use_legacy_names) { - String name = p_name.camelcase_to_underscore(true); - RegEx pattern_del("([^a-zA-Z0-9_ ])+"); - - name = pattern_del.sub(name, "", true); - - RegEx pattern_nospace(" +"); - name = pattern_nospace.sub(name, "_", true); - - RegEx pattern_multiple("_+"); - name = pattern_multiple.sub(name, "_", true); - - RegEx pattern_padded("0+(\\d+)"); - name = pattern_padded.sub(name, "$1", true); - - return name; - } else { - String name = p_name; - name = name.replace(":", "_"); - name = name.replace("/", "_"); - if (name.empty()) { - name = "bone"; - } - return name; - } -} - -String EditorSceneImporterGLTF::_gen_unique_bone_name(GLTFState &state, const GLTFSkeletonIndex skel_i, const String &p_name) { - String s_name = _sanitize_bone_name(state, p_name); - String name; - int index = 1; - while (true) { - name = s_name; - - if (index > 1) { - name += "_" + itos(index); - } - if (!state.skeletons[skel_i].unique_names.has(name)) { - break; - } - index++; - } - - state.skeletons.write[skel_i].unique_names.insert(name); - - return name; -} - -Error EditorSceneImporterGLTF::_parse_scenes(GLTFState &state) { - ERR_FAIL_COND_V(!state.json.has("scenes"), ERR_FILE_CORRUPT); - const Array &scenes = state.json["scenes"]; - int loaded_scene = 0; - if (state.json.has("scene")) { - loaded_scene = state.json["scene"]; - } else { - WARN_PRINT("The load-time scene is not defined in the glTF2 file. Picking the first scene.") - } - - if (scenes.size()) { - ERR_FAIL_COND_V(loaded_scene >= scenes.size(), ERR_FILE_CORRUPT); - const Dictionary &s = scenes[loaded_scene]; - ERR_FAIL_COND_V(!s.has("nodes"), ERR_UNAVAILABLE); - const Array &nodes = s["nodes"]; - for (int j = 0; j < nodes.size(); j++) { - state.root_nodes.push_back(nodes[j]); - } - - if (s.has("name") && s["name"] != "") { - state.scene_name = _gen_unique_name(state, s["name"]); - } else { - state.scene_name = _gen_unique_name(state, "Scene"); - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_nodes(GLTFState &state) { - ERR_FAIL_COND_V(!state.json.has("nodes"), ERR_FILE_CORRUPT); - const Array &nodes = state.json["nodes"]; - for (int i = 0; i < nodes.size(); i++) { - GLTFNode *node = memnew(GLTFNode); - const Dictionary &n = nodes[i]; - - if (n.has("name")) { - node->name = n["name"]; - } - if (n.has("camera")) { - node->camera = n["camera"]; - } - if (n.has("mesh")) { - node->mesh = n["mesh"]; - } - if (n.has("skin")) { - node->skin = n["skin"]; - } - if (n.has("matrix")) { - node->xform = _arr_to_xform(n["matrix"]); - - } else { - if (n.has("translation")) { - node->translation = _arr_to_vec3(n["translation"]); - } - if (n.has("rotation")) { - node->rotation = _arr_to_quat(n["rotation"]); - } - if (n.has("scale")) { - node->scale = _arr_to_vec3(n["scale"]); - } - - node->xform.basis.set_quat_scale(node->rotation, node->scale); - node->xform.origin = node->translation; - } - if (n.has("extensions")) { - Dictionary extensions = n["extensions"]; - if (extensions.has("KHR_lights_punctual")) { - Dictionary lights_punctual = extensions["KHR_lights_punctual"]; - if (lights_punctual.has("light")) { - GLTFLightIndex light = lights_punctual["light"]; - node->light = light; - } - } - } - if (n.has("children")) { - const Array &children = n["children"]; - for (int j = 0; j < children.size(); j++) { - node->children.push_back(children[j]); - } - } - - state.nodes.push_back(node); - } - - // build the hierarchy - for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); node_i++) { - for (int j = 0; j < state.nodes[node_i]->children.size(); j++) { - GLTFNodeIndex child_i = state.nodes[node_i]->children[j]; - - ERR_FAIL_INDEX_V(child_i, state.nodes.size(), ERR_FILE_CORRUPT); - ERR_CONTINUE(state.nodes[child_i]->parent != -1); //node already has a parent, wtf. - - state.nodes[child_i]->parent = node_i; - } - } - - _compute_node_heights(state); - - return OK; -} - -void EditorSceneImporterGLTF::_compute_node_heights(GLTFState &state) { - state.root_nodes.clear(); - for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { - GLTFNode *node = state.nodes[node_i]; - node->height = 0; - - GLTFNodeIndex current_i = node_i; - while (current_i >= 0) { - const GLTFNodeIndex parent_i = state.nodes[current_i]->parent; - if (parent_i >= 0) { - ++node->height; - } - current_i = parent_i; - } - - if (node->height == 0) { - state.root_nodes.push_back(node_i); - } - } -} - -static Vector _parse_base64_uri(const String &uri) { - int start = uri.find(","); - ERR_FAIL_COND_V(start == -1, Vector()); - - CharString substr = uri.right(start + 1).ascii(); - - int strlen = substr.length(); - - Vector buf; - buf.resize(strlen / 4 * 3 + 1 + 1); - - size_t len = 0; - ERR_FAIL_COND_V(CryptoCore::b64_decode(buf.ptrw(), buf.size(), &len, (unsigned char *)substr.get_data(), strlen) != OK, Vector()); - - buf.resize(len); - - return buf; -} - -Error EditorSceneImporterGLTF::_parse_buffers(GLTFState &state, const String &p_base_path) { - if (!state.json.has("buffers")) { - return OK; - } - - const Array &buffers = state.json["buffers"]; - for (GLTFBufferIndex i = 0; i < buffers.size(); i++) { - if (i == 0 && state.glb_data.size()) { - state.buffers.push_back(state.glb_data); - - } else { - const Dictionary &buffer = buffers[i]; - if (buffer.has("uri")) { - Vector buffer_data; - String uri = buffer["uri"]; - - if (uri.begins_with("data:")) { // Embedded data using base64. - // Validate data MIME types and throw an error if it's one we don't know/support. - if (!uri.begins_with("data:application/octet-stream;base64") && - !uri.begins_with("data:application/gltf-buffer;base64")) { - ERR_PRINT("glTF: Got buffer with an unknown URI data type: " + uri); - } - buffer_data = _parse_base64_uri(uri); - } else { // Relative path to an external image file. - uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. - buffer_data = FileAccess::get_file_as_array(uri); - ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); - } - - ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); - int byteLength = buffer["byteLength"]; - ERR_FAIL_COND_V(byteLength < buffer_data.size(), ERR_PARSE_ERROR); - state.buffers.push_back(buffer_data); - } - } - } - - print_verbose("glTF: Total buffers: " + itos(state.buffers.size())); - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_buffer_views(GLTFState &state) { - if (!state.json.has("bufferViews")) { - return OK; - } - - const Array &buffers = state.json["bufferViews"]; - for (GLTFBufferViewIndex i = 0; i < buffers.size(); i++) { - const Dictionary &d = buffers[i]; - - GLTFBufferView buffer_view; - - ERR_FAIL_COND_V(!d.has("buffer"), ERR_PARSE_ERROR); - buffer_view.buffer = d["buffer"]; - ERR_FAIL_COND_V(!d.has("byteLength"), ERR_PARSE_ERROR); - buffer_view.byte_length = d["byteLength"]; - - if (d.has("byteOffset")) { - buffer_view.byte_offset = d["byteOffset"]; - } - - if (d.has("byteStride")) { - buffer_view.byte_stride = d["byteStride"]; - } - - if (d.has("target")) { - const int target = d["target"]; - buffer_view.indices = target == ELEMENT_ARRAY_BUFFER; - } - - state.buffer_views.push_back(buffer_view); - } - - print_verbose("glTF: Total buffer views: " + itos(state.buffer_views.size())); - - return OK; -} - -EditorSceneImporterGLTF::GLTFType EditorSceneImporterGLTF::_get_type_from_str(const String &p_string) { - if (p_string == "SCALAR") { - return TYPE_SCALAR; - } - - if (p_string == "VEC2") { - return TYPE_VEC2; - } - if (p_string == "VEC3") { - return TYPE_VEC3; - } - if (p_string == "VEC4") { - return TYPE_VEC4; - } - - if (p_string == "MAT2") { - return TYPE_MAT2; - } - if (p_string == "MAT3") { - return TYPE_MAT3; - } - if (p_string == "MAT4") { - return TYPE_MAT4; - } - - ERR_FAIL_V(TYPE_SCALAR); -} - -Error EditorSceneImporterGLTF::_parse_accessors(GLTFState &state) { - if (!state.json.has("accessors")) { - return OK; - } - - const Array &accessors = state.json["accessors"]; - for (GLTFAccessorIndex i = 0; i < accessors.size(); i++) { - const Dictionary &d = accessors[i]; - - GLTFAccessor accessor; - - ERR_FAIL_COND_V(!d.has("componentType"), ERR_PARSE_ERROR); - accessor.component_type = d["componentType"]; - ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR); - accessor.count = d["count"]; - ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - accessor.type = _get_type_from_str(d["type"]); - - if (d.has("bufferView")) { - accessor.buffer_view = d["bufferView"]; //optional because it may be sparse... - } - - if (d.has("byteOffset")) { - accessor.byte_offset = d["byteOffset"]; - } - - if (d.has("normalized")) { - accessor.normalized = d["normalized"]; - } - - if (d.has("max")) { - accessor.max = d["max"]; - } - - if (d.has("min")) { - accessor.min = d["min"]; - } - - if (d.has("sparse")) { - //eeh.. - - const Dictionary &s = d["sparse"]; - - ERR_FAIL_COND_V(!s.has("count"), ERR_PARSE_ERROR); - accessor.sparse_count = s["count"]; - ERR_FAIL_COND_V(!s.has("indices"), ERR_PARSE_ERROR); - const Dictionary &si = s["indices"]; - - ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR); - accessor.sparse_indices_buffer_view = si["bufferView"]; - ERR_FAIL_COND_V(!si.has("componentType"), ERR_PARSE_ERROR); - accessor.sparse_indices_component_type = si["componentType"]; - - if (si.has("byteOffset")) { - accessor.sparse_indices_byte_offset = si["byteOffset"]; - } - - ERR_FAIL_COND_V(!s.has("values"), ERR_PARSE_ERROR); - const Dictionary &sv = s["values"]; - - ERR_FAIL_COND_V(!sv.has("bufferView"), ERR_PARSE_ERROR); - accessor.sparse_values_buffer_view = sv["bufferView"]; - if (sv.has("byteOffset")) { - accessor.sparse_values_byte_offset = sv["byteOffset"]; - } - } - - state.accessors.push_back(accessor); - } - - print_verbose("glTF: Total accessors: " + itos(state.accessors.size())); - - return OK; -} - -String EditorSceneImporterGLTF::_get_component_type_name(const uint32_t p_component) { - switch (p_component) { - case COMPONENT_TYPE_BYTE: - return "Byte"; - case COMPONENT_TYPE_UNSIGNED_BYTE: - return "UByte"; - case COMPONENT_TYPE_SHORT: - return "Short"; - case COMPONENT_TYPE_UNSIGNED_SHORT: - return "UShort"; - case COMPONENT_TYPE_INT: - return "Int"; - case COMPONENT_TYPE_FLOAT: - return "Float"; - } - - return ""; -} - -String EditorSceneImporterGLTF::_get_type_name(const GLTFType p_component) { - static const char *names[] = { - "float", - "vec2", - "vec3", - "vec4", - "mat2", - "mat3", - "mat4" - }; - - return names[p_component]; -} - -Error EditorSceneImporterGLTF::_decode_buffer_view(GLTFState &state, double *dst, const GLTFBufferViewIndex p_buffer_view, const int skip_every, const int skip_bytes, const int element_size, const int count, const GLTFType type, const int component_count, const int component_type, const int component_size, const bool normalized, const int byte_offset, const bool for_vertex) { - const GLTFBufferView &bv = state.buffer_views[p_buffer_view]; - - int stride = bv.byte_stride ? bv.byte_stride : element_size; - if (for_vertex && stride % 4) { - stride += 4 - (stride % 4); //according to spec must be multiple of 4 - } - - ERR_FAIL_INDEX_V(bv.buffer, state.buffers.size(), ERR_PARSE_ERROR); - - const uint32_t offset = bv.byte_offset + byte_offset; - Vector buffer = state.buffers[bv.buffer]; //copy on write, so no performance hit - const uint8_t *bufptr = buffer.ptr(); - - //use to debug - print_verbose("glTF: type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); - print_verbose("glTF: accessor offset" + itos(byte_offset) + " view offset: " + itos(bv.byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv.byte_length)); - - const int buffer_end = (stride * (count - 1)) + element_size; - ERR_FAIL_COND_V(buffer_end > bv.byte_length, ERR_PARSE_ERROR); - - ERR_FAIL_COND_V((int)(offset + buffer_end) > buffer.size(), ERR_PARSE_ERROR); - - //fill everything as doubles - - for (int i = 0; i < count; i++) { - const uint8_t *src = &bufptr[offset + i * stride]; - - for (int j = 0; j < component_count; j++) { - if (skip_every && j > 0 && (j % skip_every) == 0) { - src += skip_bytes; - } - - double d = 0; - - switch (component_type) { - case COMPONENT_TYPE_BYTE: { - int8_t b = int8_t(*src); - if (normalized) { - d = (double(b) / 128.0); - } else { - d = double(b); - } - } break; - case COMPONENT_TYPE_UNSIGNED_BYTE: { - uint8_t b = *src; - if (normalized) { - d = (double(b) / 255.0); - } else { - d = double(b); - } - } break; - case COMPONENT_TYPE_SHORT: { - int16_t s = *(int16_t *)src; - if (normalized) { - d = (double(s) / 32768.0); - } else { - d = double(s); - } - } break; - case COMPONENT_TYPE_UNSIGNED_SHORT: { - uint16_t s = *(uint16_t *)src; - if (normalized) { - d = (double(s) / 65535.0); - } else { - d = double(s); - } - - } break; - case COMPONENT_TYPE_INT: { - d = *(int *)src; - } break; - case COMPONENT_TYPE_FLOAT: { - d = *(float *)src; - } break; - } - - *dst++ = d; - src += component_size; - } - } - - return OK; -} - -int EditorSceneImporterGLTF::_get_component_type_size(const int component_type) { - switch (component_type) { - case COMPONENT_TYPE_BYTE: - return 1; - break; - case COMPONENT_TYPE_UNSIGNED_BYTE: - return 1; - break; - case COMPONENT_TYPE_SHORT: - return 2; - break; - case COMPONENT_TYPE_UNSIGNED_SHORT: - return 2; - break; - case COMPONENT_TYPE_INT: - return 4; - break; - case COMPONENT_TYPE_FLOAT: - return 4; - break; - default: { - ERR_FAIL_V(0); - } - } - return 0; -} - -Vector EditorSceneImporterGLTF::_decode_accessor(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - //spec, for reference: - //https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - - ERR_FAIL_INDEX_V(p_accessor, state.accessors.size(), Vector()); - - const GLTFAccessor &a = state.accessors[p_accessor]; - - const int component_count_for_type[7] = { - 1, 2, 3, 4, 4, 9, 16 - }; - - const int component_count = component_count_for_type[a.type]; - const int component_size = _get_component_type_size(a.component_type); - ERR_FAIL_COND_V(component_size == 0, Vector()); - int element_size = component_count * component_size; - - int skip_every = 0; - int skip_bytes = 0; - //special case of alignments, as described in spec - switch (a.component_type) { - case COMPONENT_TYPE_BYTE: - case COMPONENT_TYPE_UNSIGNED_BYTE: { - if (a.type == TYPE_MAT2) { - skip_every = 2; - skip_bytes = 2; - element_size = 8; //override for this case - } - if (a.type == TYPE_MAT3) { - skip_every = 3; - skip_bytes = 1; - element_size = 12; //override for this case - } - - } break; - case COMPONENT_TYPE_SHORT: - case COMPONENT_TYPE_UNSIGNED_SHORT: { - if (a.type == TYPE_MAT3) { - skip_every = 6; - skip_bytes = 4; - element_size = 16; //override for this case - } - } break; - default: { - } - } - - Vector dst_buffer; - dst_buffer.resize(component_count * a.count); - double *dst = dst_buffer.ptrw(); - - if (a.buffer_view >= 0) { - ERR_FAIL_INDEX_V(a.buffer_view, state.buffer_views.size(), Vector()); - - const Error err = _decode_buffer_view(state, dst, a.buffer_view, skip_every, skip_bytes, element_size, a.count, a.type, component_count, a.component_type, component_size, a.normalized, a.byte_offset, p_for_vertex); - if (err != OK) { - return Vector(); - } - - } else { - //fill with zeros, as bufferview is not defined. - for (int i = 0; i < (a.count * component_count); i++) { - dst_buffer.write[i] = 0; - } - } - - if (a.sparse_count > 0) { - // I could not find any file using this, so this code is so far untested - Vector indices; - indices.resize(a.sparse_count); - const int indices_component_size = _get_component_type_size(a.sparse_indices_component_type); - - Error err = _decode_buffer_view(state, indices.ptrw(), a.sparse_indices_buffer_view, 0, 0, indices_component_size, a.sparse_count, TYPE_SCALAR, 1, a.sparse_indices_component_type, indices_component_size, false, a.sparse_indices_byte_offset, false); - if (err != OK) { - return Vector(); - } - - Vector data; - data.resize(component_count * a.sparse_count); - err = _decode_buffer_view(state, data.ptrw(), a.sparse_values_buffer_view, skip_every, skip_bytes, element_size, a.sparse_count, a.type, component_count, a.component_type, component_size, a.normalized, a.sparse_values_byte_offset, p_for_vertex); - if (err != OK) { - return Vector(); - } - - for (int i = 0; i < indices.size(); i++) { - const int write_offset = int(indices[i]) * component_count; - - for (int j = 0; j < component_count; j++) { - dst[write_offset + j] = data[i * component_count + j]; - } - } - } - - return dst_buffer; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_ints(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) { - return ret; - } - - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size(); - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = int(attribs_ptr[i]); - } - } - return ret; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_floats(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) { - return ret; - } - - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size(); - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = float(attribs_ptr[i]); - } - } - return ret; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_vec2(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) { - return ret; - } - - ERR_FAIL_COND_V(attribs.size() % 2 != 0, ret); - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size() / 2; - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = Vector2(attribs_ptr[i * 2 + 0], attribs_ptr[i * 2 + 1]); - } - } - return ret; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_vec3(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) { - return ret; - } - - ERR_FAIL_COND_V(attribs.size() % 3 != 0, ret); - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size() / 3; - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = Vector3(attribs_ptr[i * 3 + 0], attribs_ptr[i * 3 + 1], attribs_ptr[i * 3 + 2]); - } - } - return ret; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_color(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) { - return ret; - } - - const int type = state.accessors[p_accessor].type; - ERR_FAIL_COND_V(!(type == TYPE_VEC3 || type == TYPE_VEC4), ret); - int vec_len = 3; - if (type == TYPE_VEC4) { - vec_len = 4; - } - - ERR_FAIL_COND_V(attribs.size() % vec_len != 0, ret); - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size() / vec_len; - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = Color(attribs_ptr[i * vec_len + 0], attribs_ptr[i * vec_len + 1], attribs_ptr[i * vec_len + 2], vec_len == 4 ? attribs_ptr[i * 4 + 3] : 1.0); - } - } - return ret; -} - -Vector EditorSceneImporterGLTF::_decode_accessor_as_quat(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - Vector ret; - - if (attribs.size() == 0) { - return ret; - } - - ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size() / 4; - ret.resize(ret_size); - { - for (int i = 0; i < ret_size; i++) { - ret.write[i] = Quat(attribs_ptr[i * 4 + 0], attribs_ptr[i * 4 + 1], attribs_ptr[i * 4 + 2], attribs_ptr[i * 4 + 3]).normalized(); - } - } - return ret; -} -Vector EditorSceneImporterGLTF::_decode_accessor_as_xform2d(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - Vector ret; - - if (attribs.size() == 0) { - return ret; - } - - ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); - ret.resize(attribs.size() / 4); - for (int i = 0; i < ret.size(); i++) { - ret.write[i][0] = Vector2(attribs[i * 4 + 0], attribs[i * 4 + 1]); - ret.write[i][1] = Vector2(attribs[i * 4 + 2], attribs[i * 4 + 3]); - } - return ret; -} - -Vector EditorSceneImporterGLTF::_decode_accessor_as_basis(GLTFState &state, const GLTFAccessorIndex p_accessor, bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - Vector ret; - - if (attribs.size() == 0) { - return ret; - } - - ERR_FAIL_COND_V(attribs.size() % 9 != 0, ret); - ret.resize(attribs.size() / 9); - for (int i = 0; i < ret.size(); i++) { - ret.write[i].set_axis(0, Vector3(attribs[i * 9 + 0], attribs[i * 9 + 1], attribs[i * 9 + 2])); - ret.write[i].set_axis(1, Vector3(attribs[i * 9 + 3], attribs[i * 9 + 4], attribs[i * 9 + 5])); - ret.write[i].set_axis(2, Vector3(attribs[i * 9 + 6], attribs[i * 9 + 7], attribs[i * 9 + 8])); - } - return ret; -} - -Vector EditorSceneImporterGLTF::_decode_accessor_as_xform(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - Vector ret; - - if (attribs.size() == 0) { - return ret; - } - - ERR_FAIL_COND_V(attribs.size() % 16 != 0, ret); - ret.resize(attribs.size() / 16); - for (int i = 0; i < ret.size(); i++) { - ret.write[i].basis.set_axis(0, Vector3(attribs[i * 16 + 0], attribs[i * 16 + 1], attribs[i * 16 + 2])); - ret.write[i].basis.set_axis(1, Vector3(attribs[i * 16 + 4], attribs[i * 16 + 5], attribs[i * 16 + 6])); - ret.write[i].basis.set_axis(2, Vector3(attribs[i * 16 + 8], attribs[i * 16 + 9], attribs[i * 16 + 10])); - ret.write[i].set_origin(Vector3(attribs[i * 16 + 12], attribs[i * 16 + 13], attribs[i * 16 + 14])); - } - return ret; -} - -Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { - if (!state.json.has("meshes")) { - return OK; - } - - bool compress_vert_data = state.import_flags & IMPORT_USE_COMPRESSION; - uint32_t mesh_flags = compress_vert_data ? Mesh::ARRAY_COMPRESS_DEFAULT : 0; - - Array meshes = state.json["meshes"]; - for (GLTFMeshIndex i = 0; i < meshes.size(); i++) { - print_verbose("glTF: Parsing mesh: " + itos(i)); - Dictionary d = meshes[i]; - - GLTFMesh mesh; - mesh.mesh.instance(); - - ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); - - Array primitives = d["primitives"]; - const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); - - for (int j = 0; j < primitives.size(); j++) { - Dictionary p = primitives[j]; - - Array array; - array.resize(Mesh::ARRAY_MAX); - - ERR_FAIL_COND_V(!p.has("attributes"), ERR_PARSE_ERROR); - - Dictionary a = p["attributes"]; - - Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; - if (p.has("mode")) { - const int mode = p["mode"]; - ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT); - static const Mesh::PrimitiveType primitives2[7] = { - Mesh::PRIMITIVE_POINTS, - Mesh::PRIMITIVE_LINES, - Mesh::PRIMITIVE_LINE_LOOP, - Mesh::PRIMITIVE_LINE_STRIP, - Mesh::PRIMITIVE_TRIANGLES, - Mesh::PRIMITIVE_TRIANGLE_STRIP, - Mesh::PRIMITIVE_TRIANGLE_FAN, - }; - - primitive = primitives2[mode]; - } - - ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR); - if (a.has("POSITION")) { - array[Mesh::ARRAY_VERTEX] = _decode_accessor_as_vec3(state, a["POSITION"], true); - } - if (a.has("NORMAL")) { - array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); - } - if (a.has("TANGENT")) { - array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(state, a["TANGENT"], true); - } - if (a.has("TEXCOORD_0")) { - array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(state, a["TEXCOORD_0"], true); - } - if (a.has("TEXCOORD_1")) { - array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(state, a["TEXCOORD_1"], true); - } - if (a.has("COLOR_0")) { - array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(state, a["COLOR_0"], true); - } - if (a.has("JOINTS_0")) { - array[Mesh::ARRAY_BONES] = _decode_accessor_as_ints(state, a["JOINTS_0"], true); - } - if (a.has("WEIGHTS_0")) { - PoolVector weights = _decode_accessor_as_floats(state, a["WEIGHTS_0"], true); - { //gltf does not seem to normalize the weights for some reason.. - int wc = weights.size(); - PoolVector::Write w = weights.write(); - - for (int k = 0; k < wc; k += 4) { - float total = 0.0; - total += w[k + 0]; - total += w[k + 1]; - total += w[k + 2]; - total += w[k + 3]; - if (total > 0.0) { - w[k + 0] /= total; - w[k + 1] /= total; - w[k + 2] /= total; - w[k + 3] /= total; - } - } - } - array[Mesh::ARRAY_WEIGHTS] = weights; - } - - if (p.has("indices")) { - PoolVector indices = _decode_accessor_as_ints(state, p["indices"], false); - - if (primitive == Mesh::PRIMITIVE_TRIANGLES) { - //swap around indices, convert ccw to cw for front face - - const int is = indices.size(); - const PoolVector::Write w = indices.write(); - for (int k = 0; k < is; k += 3) { - SWAP(w[k + 1], w[k + 2]); - } - } - array[Mesh::ARRAY_INDEX] = indices; - - } else if (primitive == Mesh::PRIMITIVE_TRIANGLES) { - //generate indices because they need to be swapped for CW/CCW - const PoolVector &vertices = array[Mesh::ARRAY_VERTEX]; - ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR); - PoolVector indices; - const int vs = vertices.size(); - indices.resize(vs); - { - const PoolVector::Write w = indices.write(); - for (int k = 0; k < vs; k += 3) { - w[k] = k; - w[k + 1] = k + 2; - w[k + 2] = k + 1; - } - } - array[Mesh::ARRAY_INDEX] = indices; - } - - bool generate_tangents = (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("TEXCOORD_0") && a.has("NORMAL")); - - if (generate_tangents) { - //must generate mikktspace tangents.. ergh.. - Ref st; - st.instance(); - st->create_from_triangle_arrays(array); - st->generate_tangents(); - array = st->commit_to_arrays(); - } - - Array morphs; - //blend shapes - if (p.has("targets")) { - print_verbose("glTF: Mesh has targets"); - const Array &targets = p["targets"]; - - //ideally BLEND_SHAPE_MODE_RELATIVE since gltf2 stores in displacement - //but it could require a larger refactor? - mesh.mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); - - if (j == 0) { - const Array &target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array(); - for (int k = 0; k < targets.size(); k++) { - const String name = k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k); - mesh.mesh->add_blend_shape(name); - } - } - - for (int k = 0; k < targets.size(); k++) { - const Dictionary &t = targets[k]; - - Array array_copy; - array_copy.resize(Mesh::ARRAY_MAX); - - for (int l = 0; l < Mesh::ARRAY_MAX; l++) { - array_copy[l] = array[l]; - } - - array_copy[Mesh::ARRAY_INDEX] = Variant(); - - if (t.has("POSITION")) { - PoolVector varr = _decode_accessor_as_vec3(state, t["POSITION"], true); - const PoolVector src_varr = array[Mesh::ARRAY_VERTEX]; - const int size = src_varr.size(); - ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); - { - const int max_idx = varr.size(); - varr.resize(size); - - const PoolVector::Write w_varr = varr.write(); - const PoolVector::Read r_varr = varr.read(); - const PoolVector::Read r_src_varr = src_varr.read(); - for (int l = 0; l < size; l++) { - if (l < max_idx) { - w_varr[l] = r_varr[l] + r_src_varr[l]; - } else { - w_varr[l] = r_src_varr[l]; - } - } - } - array_copy[Mesh::ARRAY_VERTEX] = varr; - } - if (t.has("NORMAL")) { - PoolVector narr = _decode_accessor_as_vec3(state, t["NORMAL"], true); - const PoolVector src_narr = array[Mesh::ARRAY_NORMAL]; - int size = src_narr.size(); - ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); - { - int max_idx = narr.size(); - narr.resize(size); - - const PoolVector::Write w_narr = narr.write(); - const PoolVector::Read r_narr = narr.read(); - const PoolVector::Read r_src_narr = src_narr.read(); - for (int l = 0; l < size; l++) { - if (l < max_idx) { - w_narr[l] = r_narr[l] + r_src_narr[l]; - } else { - w_narr[l] = r_src_narr[l]; - } - } - } - array_copy[Mesh::ARRAY_NORMAL] = narr; - } - if (t.has("TANGENT")) { - const PoolVector tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true); - const PoolVector src_tangents = array[Mesh::ARRAY_TANGENT]; - ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR); - - PoolVector tangents_v4; - - { - int max_idx = tangents_v3.size(); - - int size4 = src_tangents.size(); - tangents_v4.resize(size4); - const PoolVector::Write w4 = tangents_v4.write(); - - const PoolVector::Read r3 = tangents_v3.read(); - const PoolVector::Read r4 = src_tangents.read(); - - for (int l = 0; l < size4 / 4; l++) { - if (l < max_idx) { - w4[l * 4 + 0] = r3[l].x + r4[l * 4 + 0]; - w4[l * 4 + 1] = r3[l].y + r4[l * 4 + 1]; - w4[l * 4 + 2] = r3[l].z + r4[l * 4 + 2]; - } else { - w4[l * 4 + 0] = r4[l * 4 + 0]; - w4[l * 4 + 1] = r4[l * 4 + 1]; - w4[l * 4 + 2] = r4[l * 4 + 2]; - } - w4[l * 4 + 3] = r4[l * 4 + 3]; //copy flip value - } - } - - array_copy[Mesh::ARRAY_TANGENT] = tangents_v4; - } - - if (generate_tangents) { - Ref st; - st.instance(); - st->create_from_triangle_arrays(array_copy); - st->deindex(); - st->generate_tangents(); - array_copy = st->commit_to_arrays(); - } - - morphs.push_back(array_copy); - } - } - - //just add it - mesh.mesh->add_surface_from_arrays(primitive, array, morphs, mesh_flags); - - if (p.has("material")) { - const int material = p["material"]; - ERR_FAIL_INDEX_V(material, state.materials.size(), ERR_FILE_CORRUPT); - const Ref &mat = state.materials[material]; - - mesh.mesh->surface_set_material(mesh.mesh->get_surface_count() - 1, mat); - } else { - Ref mat; - mat.instance(); - mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - - mesh.mesh->surface_set_material(mesh.mesh->get_surface_count() - 1, mat); - } - } - - mesh.blend_weights.resize(mesh.mesh->get_blend_shape_count()); - for (int32_t weight_i = 0; weight_i < mesh.blend_weights.size(); weight_i++) { - mesh.blend_weights.write[weight_i] = 0.0f; - } - - if (d.has("weights")) { - const Array &weights = d["weights"]; - ERR_FAIL_COND_V(mesh.blend_weights.size() != weights.size(), ERR_PARSE_ERROR); - for (int j = 0; j < weights.size(); j++) { - mesh.blend_weights.write[j] = weights[j]; - } - } - - state.meshes.push_back(mesh); - } - - print_verbose("glTF: Total meshes: " + itos(state.meshes.size())); - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_images(GLTFState &state, const String &p_base_path) { - if (!state.json.has("images")) { - return OK; - } - - // Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#images - - const Array &images = state.json["images"]; - for (int i = 0; i < images.size(); i++) { - const Dictionary &d = images[i]; - - // glTF 2.0 supports PNG and JPEG types, which can be specified as (from spec): - // "- a URI to an external file in one of the supported images formats, or - // - a URI with embedded base64-encoded data, or - // - a reference to a bufferView; in that case mimeType must be defined." - // Since mimeType is optional for external files and base64 data, we'll have to - // fall back on letting Godot parse the data to figure out if it's PNG or JPEG. - - // We'll assume that we use either URI or bufferView, so let's warn the user - // if their image somehow uses both. And fail if it has neither. - ERR_CONTINUE_MSG(!d.has("uri") && !d.has("bufferView"), "Invalid image definition in glTF file, it should specific an 'uri' or 'bufferView'."); - if (d.has("uri") && d.has("bufferView")) { - WARN_PRINT("Invalid image definition in glTF file using both 'uri' and 'bufferView'. 'bufferView' will take precedence."); - } - - String mimetype; - if (d.has("mimeType")) { // Should be "image/png" or "image/jpeg". - mimetype = d["mimeType"]; - } - - Vector data; - const uint8_t *data_ptr = nullptr; - int data_size = 0; - - if (d.has("uri")) { - // Handles the first two bullet points from the spec (embedded data, or external file). - String uri = d["uri"]; - - if (uri.begins_with("data:")) { // Embedded data using base64. - // Validate data MIME types and throw a warning if it's one we don't know/support. - if (!uri.begins_with("data:application/octet-stream;base64") && - !uri.begins_with("data:application/gltf-buffer;base64") && - !uri.begins_with("data:image/png;base64") && - !uri.begins_with("data:image/jpeg;base64")) { - WARN_PRINT(vformat("glTF: Image index '%d' uses an unsupported URI data type: %s. Skipping it.", i, uri)); - state.images.push_back(Ref()); // Placeholder to keep count. - continue; - } - data = _parse_base64_uri(uri); - data_ptr = data.ptr(); - data_size = data.size(); - // mimeType is optional, but if we have it defined in the URI, let's use it. - if (mimetype.empty()) { - if (uri.begins_with("data:image/png;base64")) { - mimetype = "image/png"; - } else if (uri.begins_with("data:image/jpeg;base64")) { - mimetype = "image/jpeg"; - } - } - } else { // Relative path to an external image file. - uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. - // ResourceLoader will rely on the file extension to use the relevant loader. - // The spec says that if mimeType is defined, it should take precedence (e.g. - // there could be a `.png` image which is actually JPEG), but there's no easy - // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in - // the material), so we do this only as fallback. - Ref texture = ResourceLoader::load(uri); - if (texture.is_valid()) { - state.images.push_back(texture); - continue; - } else if (mimetype == "image/png" || mimetype == "image/jpeg") { - // Fallback to loading as byte array. - // This enables us to support the spec's requirement that we honor mimetype - // regardless of file URI. - data = FileAccess::get_file_as_array(uri); - if (data.size() == 0) { - WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded as a buffer of MIME type '%s' from URI: %s. Skipping it.", i, mimetype, uri)); - state.images.push_back(Ref()); // Placeholder to keep count. - continue; - } - data_ptr = data.ptr(); - data_size = data.size(); - } else { - WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded from URI: %s. Skipping it.", i, uri)); - state.images.push_back(Ref()); // Placeholder to keep count. - continue; - } - } - } else if (d.has("bufferView")) { - // Handles the third bullet point from the spec (bufferView). - ERR_FAIL_COND_V_MSG(mimetype.empty(), ERR_FILE_CORRUPT, - vformat("glTF: Image index '%d' specifies 'bufferView' but no 'mimeType', which is invalid.", i)); - - const GLTFBufferViewIndex bvi = d["bufferView"]; - - ERR_FAIL_INDEX_V(bvi, state.buffer_views.size(), ERR_PARAMETER_RANGE_ERROR); - - const GLTFBufferView &bv = state.buffer_views[bvi]; - - const GLTFBufferIndex bi = bv.buffer; - ERR_FAIL_INDEX_V(bi, state.buffers.size(), ERR_PARAMETER_RANGE_ERROR); - - ERR_FAIL_COND_V(bv.byte_offset + bv.byte_length > state.buffers[bi].size(), ERR_FILE_CORRUPT); - - data_ptr = &state.buffers[bi][bv.byte_offset]; - data_size = bv.byte_length; - } - - Ref img; - - if (mimetype == "image/png") { // Load buffer as PNG. - ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE); - img = Image::_png_mem_loader_func(data_ptr, data_size); - } else if (mimetype == "image/jpeg") { // Loader buffer as JPEG. - ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE); - img = Image::_jpg_mem_loader_func(data_ptr, data_size); - } else { - // We can land here if we got an URI with base64-encoded data with application/* MIME type, - // and the optional mimeType property was not defined to tell us how to handle this data (or was invalid). - // So let's try PNG first, then JPEG. - ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE); - img = Image::_png_mem_loader_func(data_ptr, data_size); - if (img.is_null()) { - ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE); - img = Image::_jpg_mem_loader_func(data_ptr, data_size); - } - } - - if (img.is_null()) { - ERR_PRINT(vformat("glTF: Couldn't load image index '%d' with its given mimetype: %s.", i, mimetype)); - state.images.push_back(Ref()); - continue; - } - - Ref t; - t.instance(); - t->create_from_image(img); - - state.images.push_back(t); - } - - print_verbose("glTF: Total images: " + itos(state.images.size())); - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_textures(GLTFState &state) { - if (!state.json.has("textures")) { - return OK; - } - - const Array &textures = state.json["textures"]; - for (GLTFTextureIndex i = 0; i < textures.size(); i++) { - const Dictionary &d = textures[i]; - - ERR_FAIL_COND_V(!d.has("source"), ERR_PARSE_ERROR); - - GLTFTexture t; - t.src_image = d["source"]; - state.textures.push_back(t); - } - - return OK; -} - -Ref EditorSceneImporterGLTF::_get_texture(GLTFState &state, const GLTFTextureIndex p_texture) { - ERR_FAIL_INDEX_V(p_texture, state.textures.size(), Ref()); - const GLTFImageIndex image = state.textures[p_texture].src_image; - - ERR_FAIL_INDEX_V(image, state.images.size(), Ref()); - - return state.images[image]; -} - -Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { - if (!state.json.has("materials")) { - return OK; - } - - const Array &materials = state.json["materials"]; - for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { - const Dictionary &d = materials[i]; - - Ref material; - material.instance(); - if (d.has("name")) { - material->set_name(d["name"]); - } - material->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - - if (d.has("pbrMetallicRoughness")) { - const Dictionary &mr = d["pbrMetallicRoughness"]; - if (mr.has("baseColorFactor")) { - const Array &arr = mr["baseColorFactor"]; - ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); - const Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); - - material->set_albedo(c); - } - - if (mr.has("baseColorTexture")) { - const Dictionary &bct = mr["baseColorTexture"]; - if (bct.has("index")) { - material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, _get_texture(state, bct["index"])); - } - if (!mr.has("baseColorFactor")) { - material->set_albedo(Color(1, 1, 1)); - } - } - - if (mr.has("metallicFactor")) { - material->set_metallic(mr["metallicFactor"]); - } else { - material->set_metallic(1.0); - } - - if (mr.has("roughnessFactor")) { - material->set_roughness(mr["roughnessFactor"]); - } else { - material->set_roughness(1.0); - } - - if (mr.has("metallicRoughnessTexture")) { - const Dictionary &bct = mr["metallicRoughnessTexture"]; - if (bct.has("index")) { - const Ref t = _get_texture(state, bct["index"]); - material->set_texture(SpatialMaterial::TEXTURE_METALLIC, t); - material->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); - material->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, t); - material->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); - if (!mr.has("metallicFactor")) { - material->set_metallic(1); - } - if (!mr.has("roughnessFactor")) { - material->set_roughness(1); - } - } - } - } - - if (d.has("normalTexture")) { - const Dictionary &bct = d["normalTexture"]; - if (bct.has("index")) { - material->set_texture(SpatialMaterial::TEXTURE_NORMAL, _get_texture(state, bct["index"])); - material->set_feature(SpatialMaterial::FEATURE_NORMAL_MAPPING, true); - } - if (bct.has("scale")) { - material->set_normal_scale(bct["scale"]); - } - } - if (d.has("occlusionTexture")) { - const Dictionary &bct = d["occlusionTexture"]; - if (bct.has("index")) { - material->set_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION, _get_texture(state, bct["index"])); - material->set_ao_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_RED); - material->set_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION, true); - } - } - - if (d.has("emissiveFactor")) { - const Array &arr = d["emissiveFactor"]; - ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); - const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); - material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); - - material->set_emission(c); - } - - if (d.has("emissiveTexture")) { - const Dictionary &bct = d["emissiveTexture"]; - if (bct.has("index")) { - material->set_texture(SpatialMaterial::TEXTURE_EMISSION, _get_texture(state, bct["index"])); - material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); - material->set_emission(Color(0, 0, 0)); - } - } - - if (d.has("doubleSided")) { - const bool ds = d["doubleSided"]; - if (ds) { - material->set_cull_mode(SpatialMaterial::CULL_DISABLED); - } - } - - if (d.has("alphaMode")) { - const String &am = d["alphaMode"]; - if (am == "BLEND") { - material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - material->set_depth_draw_mode(SpatialMaterial::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } else if (am == "MASK") { - material->set_flag(SpatialMaterial::FLAG_USE_ALPHA_SCISSOR, true); - if (d.has("alphaCutoff")) { - material->set_alpha_scissor_threshold(d["alphaCutoff"]); - } else { - material->set_alpha_scissor_threshold(0.5f); - } - } - } - - state.materials.push_back(material); - } - - print_verbose("glTF: Total materials: " + itos(state.materials.size())); - - return OK; -} - -EditorSceneImporterGLTF::GLTFNodeIndex EditorSceneImporterGLTF::_find_highest_node(GLTFState &state, const Vector &subset) { - int highest = -1; - GLTFNodeIndex best_node = -1; - - for (int i = 0; i < subset.size(); ++i) { - const GLTFNodeIndex node_i = subset[i]; - const GLTFNode *node = state.nodes[node_i]; - - if (highest == -1 || node->height < highest) { - highest = node->height; - best_node = node_i; - } - } - - return best_node; -} - -bool EditorSceneImporterGLTF::_capture_nodes_in_skin(GLTFState &state, GLTFSkin &skin, const GLTFNodeIndex node_index) { - bool found_joint = false; - - for (int i = 0; i < state.nodes[node_index]->children.size(); ++i) { - found_joint |= _capture_nodes_in_skin(state, skin, state.nodes[node_index]->children[i]); - } - - if (found_joint) { - // Mark it if we happen to find another skins joint... - if (state.nodes[node_index]->joint && skin.joints.find(node_index) < 0) { - skin.joints.push_back(node_index); - } else if (skin.non_joints.find(node_index) < 0) { - skin.non_joints.push_back(node_index); - } - } - - if (skin.joints.find(node_index) > 0) { - return true; - } - - return false; -} - -void EditorSceneImporterGLTF::_capture_nodes_for_multirooted_skin(GLTFState &state, GLTFSkin &skin) { - DisjointSet disjoint_set; - - for (int i = 0; i < skin.joints.size(); ++i) { - const GLTFNodeIndex node_index = skin.joints[i]; - const GLTFNodeIndex parent = state.nodes[node_index]->parent; - disjoint_set.insert(node_index); - - if (skin.joints.find(parent) >= 0) { - disjoint_set.create_union(parent, node_index); - } - } - - Vector roots; - disjoint_set.get_representatives(roots); - - if (roots.size() <= 1) { - return; - } - - int maxHeight = -1; - - // Determine the max height rooted tree - for (int i = 0; i < roots.size(); ++i) { - const GLTFNodeIndex root = roots[i]; - - if (maxHeight == -1 || state.nodes[root]->height < maxHeight) { - maxHeight = state.nodes[root]->height; - } - } - - // Go up the tree till all of the multiple roots of the skin are at the same hierarchy level. - // This sucks, but 99% of all game engines (not just Godot) would have this same issue. - for (int i = 0; i < roots.size(); ++i) { - GLTFNodeIndex current_node = roots[i]; - while (state.nodes[current_node]->height > maxHeight) { - GLTFNodeIndex parent = state.nodes[current_node]->parent; - - if (state.nodes[parent]->joint && skin.joints.find(parent) < 0) { - skin.joints.push_back(parent); - } else if (skin.non_joints.find(parent) < 0) { - skin.non_joints.push_back(parent); - } - - current_node = parent; - } - - // replace the roots - roots.write[i] = current_node; - } - - // Climb up the tree until they all have the same parent - bool all_same; - - do { - all_same = true; - const GLTFNodeIndex first_parent = state.nodes[roots[0]]->parent; - - for (int i = 1; i < roots.size(); ++i) { - all_same &= (first_parent == state.nodes[roots[i]]->parent); - } - - if (!all_same) { - for (int i = 0; i < roots.size(); ++i) { - const GLTFNodeIndex current_node = roots[i]; - const GLTFNodeIndex parent = state.nodes[current_node]->parent; - - if (state.nodes[parent]->joint && skin.joints.find(parent) < 0) { - skin.joints.push_back(parent); - } else if (skin.non_joints.find(parent) < 0) { - skin.non_joints.push_back(parent); - } - - roots.write[i] = parent; - } - } - - } while (!all_same); -} - -Error EditorSceneImporterGLTF::_expand_skin(GLTFState &state, GLTFSkin &skin) { - _capture_nodes_for_multirooted_skin(state, skin); - - // Grab all nodes that lay in between skin joints/nodes - DisjointSet disjoint_set; - - Vector all_skin_nodes; - all_skin_nodes.append_array(skin.joints); - all_skin_nodes.append_array(skin.non_joints); - - for (int i = 0; i < all_skin_nodes.size(); ++i) { - const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state.nodes[node_index]->parent; - disjoint_set.insert(node_index); - - if (all_skin_nodes.find(parent) >= 0) { - disjoint_set.create_union(parent, node_index); - } - } - - Vector out_owners; - disjoint_set.get_representatives(out_owners); - - Vector out_roots; - - for (int i = 0; i < out_owners.size(); ++i) { - Vector set; - disjoint_set.get_members(set, out_owners[i]); - - const GLTFNodeIndex root = _find_highest_node(state, set); - ERR_FAIL_COND_V(root < 0, FAILED); - out_roots.push_back(root); - } - - out_roots.sort(); - - for (int i = 0; i < out_roots.size(); ++i) { - _capture_nodes_in_skin(state, skin, out_roots[i]); - } - - skin.roots = out_roots; - - return OK; -} - -Error EditorSceneImporterGLTF::_verify_skin(GLTFState &state, GLTFSkin &skin) { - // This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is) - // In case additional interpolating logic is added to the skins, this will help ensure that you - // do not cause it to self implode into a fiery blaze - - // We are going to re-calculate the root nodes and compare them to the ones saved in the skin, - // then ensure the multiple trees (if they exist) are on the same sublevel - - // Grab all nodes that lay in between skin joints/nodes - DisjointSet disjoint_set; - - Vector all_skin_nodes; - all_skin_nodes.append_array(skin.joints); - all_skin_nodes.append_array(skin.non_joints); - - for (int i = 0; i < all_skin_nodes.size(); ++i) { - const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state.nodes[node_index]->parent; - disjoint_set.insert(node_index); - - if (all_skin_nodes.find(parent) >= 0) { - disjoint_set.create_union(parent, node_index); - } - } - - Vector out_owners; - disjoint_set.get_representatives(out_owners); - - Vector out_roots; - - for (int i = 0; i < out_owners.size(); ++i) { - Vector set; - disjoint_set.get_members(set, out_owners[i]); - - const GLTFNodeIndex root = _find_highest_node(state, set); - ERR_FAIL_COND_V(root < 0, FAILED); - out_roots.push_back(root); - } - - out_roots.sort(); - - ERR_FAIL_COND_V(out_roots.size() == 0, FAILED); - - // Make sure the roots are the exact same (they better be) - ERR_FAIL_COND_V(out_roots.size() != skin.roots.size(), FAILED); - for (int i = 0; i < out_roots.size(); ++i) { - ERR_FAIL_COND_V(out_roots[i] != skin.roots[i], FAILED); - } - - // Single rooted skin? Perfectly ok! - if (out_roots.size() == 1) { - return OK; - } - - // Make sure all parents of a multi-rooted skin are the SAME - const GLTFNodeIndex parent = state.nodes[out_roots[0]]->parent; - for (int i = 1; i < out_roots.size(); ++i) { - if (state.nodes[out_roots[i]]->parent != parent) { - return FAILED; - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_skins(GLTFState &state) { - if (!state.json.has("skins")) { - return OK; - } - - const Array &skins = state.json["skins"]; - - // Create the base skins, and mark nodes that are joints - for (int i = 0; i < skins.size(); i++) { - const Dictionary &d = skins[i]; - - GLTFSkin skin; - - ERR_FAIL_COND_V(!d.has("joints"), ERR_PARSE_ERROR); - - const Array &joints = d["joints"]; - - if (d.has("inverseBindMatrices")) { - skin.inverse_binds = _decode_accessor_as_xform(state, d["inverseBindMatrices"], false); - ERR_FAIL_COND_V(skin.inverse_binds.size() != joints.size(), ERR_PARSE_ERROR); - } - - for (int j = 0; j < joints.size(); j++) { - const GLTFNodeIndex node = joints[j]; - ERR_FAIL_INDEX_V(node, state.nodes.size(), ERR_PARSE_ERROR); - - skin.joints.push_back(node); - skin.joints_original.push_back(node); - - state.nodes[node]->joint = true; - } - - if (d.has("name")) { - skin.name = d["name"]; - } - - if (d.has("skeleton")) { - skin.skin_root = d["skeleton"]; - } - - state.skins.push_back(skin); - } - - for (GLTFSkinIndex i = 0; i < state.skins.size(); ++i) { - GLTFSkin &skin = state.skins.write[i]; - - // Expand the skin to capture all the extra non-joints that lie in between the actual joints, - // and expand the hierarchy to ensure multi-rooted trees lie on the same height level - ERR_FAIL_COND_V(_expand_skin(state, skin), ERR_PARSE_ERROR); - ERR_FAIL_COND_V(_verify_skin(state, skin), ERR_PARSE_ERROR); - } - - print_verbose("glTF: Total skins: " + itos(state.skins.size())); - - return OK; -} - -Error EditorSceneImporterGLTF::_determine_skeletons(GLTFState &state) { - // Using a disjoint set, we are going to potentially combine all skins that are actually branches - // of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton. - // This is another unclear issue caused by the current glTF specification. - - DisjointSet skeleton_sets; - - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - const GLTFSkin &skin = state.skins[skin_i]; - - Vector all_skin_nodes; - all_skin_nodes.append_array(skin.joints); - all_skin_nodes.append_array(skin.non_joints); - - for (int i = 0; i < all_skin_nodes.size(); ++i) { - const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state.nodes[node_index]->parent; - skeleton_sets.insert(node_index); - - if (all_skin_nodes.find(parent) >= 0) { - skeleton_sets.create_union(parent, node_index); - } - } - - // We are going to connect the separate skin subtrees in each skin together - // so that the final roots are entire sets of valid skin trees - for (int i = 1; i < skin.roots.size(); ++i) { - skeleton_sets.create_union(skin.roots[0], skin.roots[i]); - } - } - - { // attempt to joint all touching subsets (siblings/parent are part of another skin) - Vector groups_representatives; - skeleton_sets.get_representatives(groups_representatives); - - Vector highest_group_members; - Vector> groups; - for (int i = 0; i < groups_representatives.size(); ++i) { - Vector group; - skeleton_sets.get_members(group, groups_representatives[i]); - highest_group_members.push_back(_find_highest_node(state, group)); - groups.push_back(group); - } - - for (int i = 0; i < highest_group_members.size(); ++i) { - const GLTFNodeIndex node_i = highest_group_members[i]; - - // Attach any siblings together (this needs to be done n^2/2 times) - for (int j = i + 1; j < highest_group_members.size(); ++j) { - const GLTFNodeIndex node_j = highest_group_members[j]; - - // Even if they are siblings under the root! :) - if (state.nodes[node_i]->parent == state.nodes[node_j]->parent) { - skeleton_sets.create_union(node_i, node_j); - } - } - - // Attach any parenting going on together (we need to do this n^2 times) - const GLTFNodeIndex node_i_parent = state.nodes[node_i]->parent; - if (node_i_parent >= 0) { - for (int j = 0; j < groups.size() && i != j; ++j) { - const Vector &group = groups[j]; - - if (group.find(node_i_parent) >= 0) { - const GLTFNodeIndex node_j = highest_group_members[j]; - skeleton_sets.create_union(node_i, node_j); - } - } - } - } - } - - // At this point, the skeleton groups should be finalized - Vector skeleton_owners; - skeleton_sets.get_representatives(skeleton_owners); - - // Mark all the skins actual skeletons, after we have merged them - for (GLTFSkeletonIndex skel_i = 0; skel_i < skeleton_owners.size(); ++skel_i) { - const GLTFNodeIndex skeleton_owner = skeleton_owners[skel_i]; - GLTFSkeleton skeleton; - - Vector skeleton_nodes; - skeleton_sets.get_members(skeleton_nodes, skeleton_owner); - - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - GLTFSkin &skin = state.skins.write[skin_i]; - - // If any of the the skeletons nodes exist in a skin, that skin now maps to the skeleton - for (int i = 0; i < skeleton_nodes.size(); ++i) { - GLTFNodeIndex skel_node_i = skeleton_nodes[i]; - if (skin.joints.find(skel_node_i) >= 0 || skin.non_joints.find(skel_node_i) >= 0) { - skin.skeleton = skel_i; - continue; - } - } - } - - Vector non_joints; - for (int i = 0; i < skeleton_nodes.size(); ++i) { - const GLTFNodeIndex node_i = skeleton_nodes[i]; - - if (state.nodes[node_i]->joint) { - skeleton.joints.push_back(node_i); - } else { - non_joints.push_back(node_i); - } - } - - state.skeletons.push_back(skeleton); - - _reparent_non_joint_skeleton_subtrees(state, state.skeletons.write[skel_i], non_joints); - } - - for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); ++skel_i) { - GLTFSkeleton &skeleton = state.skeletons.write[skel_i]; - - for (int i = 0; i < skeleton.joints.size(); ++i) { - const GLTFNodeIndex node_i = skeleton.joints[i]; - GLTFNode *node = state.nodes[node_i]; - - ERR_FAIL_COND_V(!node->joint, ERR_PARSE_ERROR); - ERR_FAIL_COND_V(node->skeleton >= 0, ERR_PARSE_ERROR); - node->skeleton = skel_i; - } - - ERR_FAIL_COND_V(_determine_skeleton_roots(state, skel_i), ERR_PARSE_ERROR); - } - - return OK; -} - -Error EditorSceneImporterGLTF::_reparent_non_joint_skeleton_subtrees(GLTFState &state, GLTFSkeleton &skeleton, const Vector &non_joints) { - DisjointSet subtree_set; - - // Populate the disjoint set with ONLY non joints that are in the skeleton hierarchy (non_joints vector) - // This way we can find any joints that lie in between joints, as the current glTF specification - // mentions nothing about non-joints being in between joints of the same skin. Hopefully one day we - // can remove this code. - - // skinD depicted here explains this issue: - // https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Animation_Skin - - for (int i = 0; i < non_joints.size(); ++i) { - const GLTFNodeIndex node_i = non_joints[i]; - - subtree_set.insert(node_i); - - const GLTFNodeIndex parent_i = state.nodes[node_i]->parent; - if (parent_i >= 0 && non_joints.find(parent_i) >= 0 && !state.nodes[parent_i]->joint) { - subtree_set.create_union(parent_i, node_i); - } - } - - // Find all the non joint subtrees and re-parent them to a new "fake" joint - - Vector non_joint_subtree_roots; - subtree_set.get_representatives(non_joint_subtree_roots); - - for (int root_i = 0; root_i < non_joint_subtree_roots.size(); ++root_i) { - const GLTFNodeIndex subtree_root = non_joint_subtree_roots[root_i]; - - Vector subtree_nodes; - subtree_set.get_members(subtree_nodes, subtree_root); - - for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) { - GLTFNode *node = state.nodes[subtree_nodes[subtree_i]]; - node->joint = true; - // Add the joint to the skeletons joints - skeleton.joints.push_back(subtree_nodes[subtree_i]); - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_determine_skeleton_roots(GLTFState &state, const GLTFSkeletonIndex skel_i) { - DisjointSet disjoint_set; - - for (GLTFNodeIndex i = 0; i < state.nodes.size(); ++i) { - const GLTFNode *node = state.nodes[i]; - - if (node->skeleton != skel_i) { - continue; - } - - disjoint_set.insert(i); - - if (node->parent >= 0 && state.nodes[node->parent]->skeleton == skel_i) { - disjoint_set.create_union(node->parent, i); - } - } - - GLTFSkeleton &skeleton = state.skeletons.write[skel_i]; - - Vector owners; - disjoint_set.get_representatives(owners); - - Vector roots; - - for (int i = 0; i < owners.size(); ++i) { - Vector set; - disjoint_set.get_members(set, owners[i]); - const GLTFNodeIndex root = _find_highest_node(state, set); - ERR_FAIL_COND_V(root < 0, FAILED); - roots.push_back(root); - } - - roots.sort(); - - skeleton.roots = roots; - - if (roots.size() == 0) { - return FAILED; - } else if (roots.size() == 1) { - return OK; - } - - // Check that the subtrees have the same parent root - const GLTFNodeIndex parent = state.nodes[roots[0]]->parent; - for (int i = 1; i < roots.size(); ++i) { - if (state.nodes[roots[i]]->parent != parent) { - return FAILED; - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_create_skeletons(GLTFState &state) { - for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); ++skel_i) { - GLTFSkeleton &gltf_skeleton = state.skeletons.write[skel_i]; - - Skeleton *skeleton = memnew(Skeleton); - gltf_skeleton.godot_skeleton = skeleton; - - // Make a unique name, no gltf node represents this skeleton - skeleton->set_name(_gen_unique_name(state, "Skeleton")); - - List bones; - - for (int i = 0; i < gltf_skeleton.roots.size(); ++i) { - bones.push_back(gltf_skeleton.roots[i]); - } - - // Make the skeleton creation deterministic by going through the roots in - // a sorted order, and DEPTH FIRST - bones.sort(); - - while (!bones.empty()) { - const GLTFNodeIndex node_i = bones.front()->get(); - bones.pop_front(); - - GLTFNode *node = state.nodes[node_i]; - ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED); - - { // Add all child nodes to the stack (deterministically) - Vector child_nodes; - for (int i = 0; i < node->children.size(); ++i) { - const GLTFNodeIndex child_i = node->children[i]; - if (state.nodes[child_i]->skeleton == skel_i) { - child_nodes.push_back(child_i); - } - } - - // Depth first insertion - child_nodes.sort(); - for (int i = child_nodes.size() - 1; i >= 0; --i) { - bones.push_front(child_nodes[i]); - } - } - - const int bone_index = skeleton->get_bone_count(); - - if (node->name.empty()) { - node->name = "bone"; - } - - node->name = _gen_unique_bone_name(state, skel_i, node->name); - - skeleton->add_bone(node->name); - skeleton->set_bone_rest(bone_index, node->xform); - - if (node->parent >= 0 && state.nodes[node->parent]->skeleton == skel_i) { - const int bone_parent = skeleton->find_bone(state.nodes[node->parent]->name); - ERR_FAIL_COND_V(bone_parent < 0, FAILED); - skeleton->set_bone_parent(bone_index, skeleton->find_bone(state.nodes[node->parent]->name)); - } - - state.scene_nodes.insert(node_i, skeleton); - } - } - - ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(state), ERR_PARSE_ERROR); - - return OK; -} - -Error EditorSceneImporterGLTF::_map_skin_joints_indices_to_skeleton_bone_indices(GLTFState &state) { - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - GLTFSkin &skin = state.skins.write[skin_i]; - - const GLTFSkeleton &skeleton = state.skeletons[skin.skeleton]; - - for (int joint_index = 0; joint_index < skin.joints_original.size(); ++joint_index) { - const GLTFNodeIndex node_i = skin.joints_original[joint_index]; - const GLTFNode *node = state.nodes[node_i]; - - skin.joint_i_to_name.insert(joint_index, node->name); - - const int bone_index = skeleton.godot_skeleton->find_bone(node->name); - ERR_FAIL_COND_V(bone_index < 0, FAILED); - - skin.joint_i_to_bone_i.insert(joint_index, bone_index); - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_create_skins(GLTFState &state) { - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - GLTFSkin &gltf_skin = state.skins.write[skin_i]; - - Ref skin; - skin.instance(); - - // Some skins don't have IBM's! What absolute monsters! - const bool has_ibms = !gltf_skin.inverse_binds.empty(); - - for (int joint_i = 0; joint_i < gltf_skin.joints_original.size(); ++joint_i) { - Transform xform; - if (has_ibms) { - xform = gltf_skin.inverse_binds[joint_i]; - } - - if (state.use_named_skin_binds) { - StringName name = gltf_skin.joint_i_to_name[joint_i]; - skin->add_named_bind(name, xform); - } else { - int bone_i = gltf_skin.joint_i_to_bone_i[joint_i]; - skin->add_bind(bone_i, xform); - } - } - - gltf_skin.godot_skin = skin; - } - - // Purge the duplicates! - _remove_duplicate_skins(state); - - // Create unique names now, after removing duplicates - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - Ref skin = state.skins[skin_i].godot_skin; - if (skin->get_name().empty()) { - // Make a unique name, no gltf node represents this skin - skin->set_name(_gen_unique_name(state, "Skin")); - } - } - - return OK; -} - -bool EditorSceneImporterGLTF::_skins_are_same(const Ref &skin_a, const Ref &skin_b) { - if (skin_a->get_bind_count() != skin_b->get_bind_count()) { - return false; - } - - for (int i = 0; i < skin_a->get_bind_count(); ++i) { - if (skin_a->get_bind_bone(i) != skin_b->get_bind_bone(i)) { - return false; - } - if (skin_a->get_bind_name(i) != skin_b->get_bind_name(i)) { - return false; - } - - Transform a_xform = skin_a->get_bind_pose(i); - Transform b_xform = skin_b->get_bind_pose(i); - - if (a_xform != b_xform) { - return false; - } - } - - return true; -} - -void EditorSceneImporterGLTF::_remove_duplicate_skins(GLTFState &state) { - for (int i = 0; i < state.skins.size(); ++i) { - for (int j = i + 1; j < state.skins.size(); ++j) { - const Ref &skin_i = state.skins[i].godot_skin; - const Ref &skin_j = state.skins[j].godot_skin; - - if (_skins_are_same(skin_i, skin_j)) { - // replace it and delete the old - state.skins.write[j].godot_skin = skin_i; - } - } - } -} - -Error EditorSceneImporterGLTF::_parse_lights(GLTFState &state) { - if (!state.json.has("extensions")) { - return OK; - } - Dictionary extensions = state.json["extensions"]; - if (!extensions.has("KHR_lights_punctual")) { - return OK; - } - Dictionary lights_punctual = extensions["KHR_lights_punctual"]; - if (!lights_punctual.has("lights")) { - return OK; - } - - const Array &lights = lights_punctual["lights"]; - - for (GLTFLightIndex light_i = 0; light_i < lights.size(); light_i++) { - const Dictionary &d = lights[light_i]; - - GLTFLight light; - ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - const String &type = d["type"]; - light.type = type; - - if (d.has("color")) { - const Array &arr = d["color"]; - ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); - const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); - light.color = c; - } - if (d.has("intensity")) { - light.intensity = d["intensity"]; - } - if (d.has("range")) { - light.range = d["range"]; - } - if (type == "spot") { - const Dictionary &spot = d["spot"]; - light.inner_cone_angle = spot["innerConeAngle"]; - light.outer_cone_angle = spot["outerConeAngle"]; - ERR_FAIL_COND_V_MSG(light.inner_cone_angle >= light.outer_cone_angle, ERR_PARSE_ERROR, "The inner angle must be smaller than the outer angle."); - } else if (type != "point" && type != "directional") { - ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Light type is unknown."); - } - - state.lights.push_back(light); - } - - print_verbose("glTF: Total lights: " + itos(state.lights.size())); - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_cameras(GLTFState &state) { - if (!state.json.has("cameras")) { - return OK; - } - - const Array &cameras = state.json["cameras"]; - - for (GLTFCameraIndex i = 0; i < cameras.size(); i++) { - const Dictionary &d = cameras[i]; - - GLTFCamera camera; - ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - const String &type = d["type"]; - if (type == "orthographic") { - camera.perspective = false; - if (d.has("orthographic")) { - const Dictionary &og = d["orthographic"]; - camera.fov_size = og["ymag"]; - camera.zfar = og["zfar"]; - camera.znear = og["znear"]; - } else { - camera.fov_size = 10; - } - - } else if (type == "perspective") { - camera.perspective = true; - if (d.has("perspective")) { - const Dictionary &ppt = d["perspective"]; - // GLTF spec is in radians, Godot's camera is in degrees. - camera.fov_size = (double)ppt["yfov"] * 180.0 / Math_PI; - camera.zfar = ppt["zfar"]; - camera.znear = ppt["znear"]; - } else { - camera.fov_size = 10; - } - } else { - ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Camera should be in 'orthographic' or 'perspective'"); - } - - state.cameras.push_back(camera); - } - - print_verbose("glTF: Total cameras: " + itos(state.cameras.size())); - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_animations(GLTFState &state) { - if (!state.json.has("animations")) { - return OK; - } - - const Array &animations = state.json["animations"]; - - for (GLTFAnimationIndex i = 0; i < animations.size(); i++) { - const Dictionary &d = animations[i]; - - GLTFAnimation animation; - - if (!d.has("channels") || !d.has("samplers")) { - continue; - } - - Array channels = d["channels"]; - Array samplers = d["samplers"]; - - if (d.has("name")) { - String name = d["name"]; - if (name.begins_with("loop") || name.ends_with("loop") || name.begins_with("cycle") || name.ends_with("cycle")) { - animation.loop = true; - } - if (state.use_legacy_names) { - animation.name = _sanitize_scene_name(state, name); - } else { - animation.name = _gen_unique_animation_name(state, name); - } - } - - for (int j = 0; j < channels.size(); j++) { - const Dictionary &c = channels[j]; - if (!c.has("target")) { - continue; - } - - const Dictionary &t = c["target"]; - if (!t.has("node") || !t.has("path")) { - continue; - } - - ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR); - const int sampler = c["sampler"]; - ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR); - - GLTFNodeIndex node = t["node"]; - String path = t["path"]; - - ERR_FAIL_INDEX_V(node, state.nodes.size(), ERR_PARSE_ERROR); - - GLTFAnimation::Track *track = nullptr; - - if (!animation.tracks.has(node)) { - animation.tracks[node] = GLTFAnimation::Track(); - } - - track = &animation.tracks[node]; - - const Dictionary &s = samplers[sampler]; - - ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR); - ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR); - - const int input = s["input"]; - const int output = s["output"]; - - GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR; - int output_count = 1; - if (s.has("interpolation")) { - const String &in = s["interpolation"]; - if (in == "STEP") { - interp = GLTFAnimation::INTERP_STEP; - } else if (in == "LINEAR") { - interp = GLTFAnimation::INTERP_LINEAR; - } else if (in == "CATMULLROMSPLINE") { - interp = GLTFAnimation::INTERP_CATMULLROMSPLINE; - output_count = 3; - } else if (in == "CUBICSPLINE") { - interp = GLTFAnimation::INTERP_CUBIC_SPLINE; - output_count = 3; - } - } - - const PoolVector times = _decode_accessor_as_floats(state, input, false); - if (path == "translation") { - const PoolVector translations = _decode_accessor_as_vec3(state, output, false); - track->translation_track.interpolation = interp; - track->translation_track.times = Variant(times); //convert via variant - track->translation_track.values = Variant(translations); //convert via variant - } else if (path == "rotation") { - const Vector rotations = _decode_accessor_as_quat(state, output, false); - track->rotation_track.interpolation = interp; - track->rotation_track.times = Variant(times); //convert via variant - track->rotation_track.values = rotations; //convert via variant - } else if (path == "scale") { - const PoolVector scales = _decode_accessor_as_vec3(state, output, false); - track->scale_track.interpolation = interp; - track->scale_track.times = Variant(times); //convert via variant - track->scale_track.values = Variant(scales); //convert via variant - } else if (path == "weights") { - const PoolVector weights = _decode_accessor_as_floats(state, output, false); - - ERR_FAIL_INDEX_V(state.nodes[node]->mesh, state.meshes.size(), ERR_PARSE_ERROR); - const GLTFMesh *mesh = &state.meshes[state.nodes[node]->mesh]; - ERR_FAIL_COND_V(mesh->blend_weights.size() == 0, ERR_PARSE_ERROR); - const int wc = mesh->blend_weights.size(); - - track->weight_tracks.resize(wc); - - const int expected_value_count = times.size() * output_count * wc; - ERR_FAIL_COND_V_MSG(weights.size() != expected_value_count, ERR_PARSE_ERROR, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead."); - - const int wlen = weights.size() / wc; - PoolVector::Read r = weights.read(); - for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea - GLTFAnimation::Channel cf; - cf.interpolation = interp; - cf.times = Variant(times); - Vector wdata; - wdata.resize(wlen); - for (int l = 0; l < wlen; l++) { - wdata.write[l] = r[l * wc + k]; - } - - cf.values = wdata; - track->weight_tracks.write[k] = cf; - } - } else { - WARN_PRINTS("Invalid path '" + path + "'."); - } - } - - state.animations.push_back(animation); - } - - print_verbose("glTF: Total animations '" + itos(state.animations.size()) + "'."); - - return OK; -} - -void EditorSceneImporterGLTF::_assign_scene_names(GLTFState &state) { - for (int i = 0; i < state.nodes.size(); i++) { - GLTFNode *n = state.nodes[i]; - - // Any joints get unique names generated when the skeleton is made, unique to the skeleton - if (n->skeleton >= 0) { - continue; - } - - if (n->name.empty()) { - if (n->mesh >= 0) { - n->name = "Mesh"; - } else if (n->camera >= 0) { - n->name = "Camera"; - } else { - n->name = "Node"; - } - } - - n->name = _gen_unique_name(state, n->name); - } -} - -BoneAttachment *EditorSceneImporterGLTF::_generate_bone_attachment(GLTFState &state, Skeleton *skeleton, const GLTFNodeIndex node_index, const GLTFNodeIndex bone_index) { - const GLTFNode *gltf_node = state.nodes[node_index]; - const GLTFNode *bone_node = state.nodes[bone_index]; - BoneAttachment *bone_attachment = memnew(BoneAttachment); - print_verbose("glTF: Creating bone attachment for: " + gltf_node->name); - - ERR_FAIL_COND_V(!bone_node->joint, nullptr); - - bone_attachment->set_bone_name(bone_node->name); - - return bone_attachment; -} - -MeshInstance *EditorSceneImporterGLTF::_generate_mesh_instance(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { - const GLTFNode *gltf_node = state.nodes[node_index]; - - ERR_FAIL_INDEX_V(gltf_node->mesh, state.meshes.size(), nullptr); - - MeshInstance *mi = memnew(MeshInstance); - print_verbose("glTF: Creating mesh for: " + gltf_node->name); - - GLTFMesh &mesh = state.meshes.write[gltf_node->mesh]; - mi->set_mesh(mesh.mesh); - - if (mesh.mesh->get_name() == "") { - mesh.mesh->set_name(gltf_node->name); - } - - for (int i = 0; i < mesh.blend_weights.size(); i++) { - mi->set("blend_shapes/" + mesh.mesh->get_blend_shape_name(i), mesh.blend_weights[i]); - } - - return mi; -} - -Spatial *EditorSceneImporterGLTF::_generate_light(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { - const GLTFNode *gltf_node = state.nodes[node_index]; - - ERR_FAIL_INDEX_V(gltf_node->light, state.lights.size(), nullptr); - - print_verbose("glTF: Creating light for: " + gltf_node->name); - - const GLTFLight &l = state.lights[gltf_node->light]; - - float intensity = l.intensity; - if (intensity > 10) { - // GLTF spec has the default around 1, but Blender defaults lights to 100. - // The only sane way to handle this is to check where it came from and - // handle it accordingly. If it's over 10, it probably came from Blender. - intensity /= 100; - } - - if (l.type == "directional") { - DirectionalLight *light = memnew(DirectionalLight); - light->set_param(Light::PARAM_ENERGY, intensity); - light->set_color(l.color); - return light; - } - - const float range = CLAMP(l.range, 0, 4096); - // Doubling the range will double the effective brightness, so we need double attenuation (half brightness). - // We want to have double intensity give double brightness, so we need half the attenuation. - const float attenuation = range / intensity; - if (l.type == "point") { - OmniLight *light = memnew(OmniLight); - light->set_param(OmniLight::PARAM_ATTENUATION, attenuation); - light->set_param(OmniLight::PARAM_RANGE, range); - light->set_color(l.color); - return light; - } - if (l.type == "spot") { - SpotLight *light = memnew(SpotLight); - light->set_param(SpotLight::PARAM_ATTENUATION, attenuation); - light->set_param(SpotLight::PARAM_RANGE, range); - light->set_param(SpotLight::PARAM_SPOT_ANGLE, Math::rad2deg(l.outer_cone_angle)); - light->set_color(l.color); - - // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b - // The points in desmos are not exact, except for (1, infinity). - float angle_ratio = l.inner_cone_angle / l.outer_cone_angle; - float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1; - light->set_param(SpotLight::PARAM_SPOT_ATTENUATION, angle_attenuation); - return light; - } - return memnew(Spatial); -} - -Camera *EditorSceneImporterGLTF::_generate_camera(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { - const GLTFNode *gltf_node = state.nodes[node_index]; - - ERR_FAIL_INDEX_V(gltf_node->camera, state.cameras.size(), nullptr); - - Camera *camera = memnew(Camera); - print_verbose("glTF: Creating camera for: " + gltf_node->name); - - const GLTFCamera &c = state.cameras[gltf_node->camera]; - if (c.perspective) { - camera->set_perspective(c.fov_size, c.znear, c.zfar); - } else { - camera->set_orthogonal(c.fov_size, c.znear, c.zfar); - } - - return camera; -} - -Spatial *EditorSceneImporterGLTF::_generate_spatial(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { - const GLTFNode *gltf_node = state.nodes[node_index]; - - Spatial *spatial = memnew(Spatial); - print_verbose("glTF: Creating spatial for: " + gltf_node->name); - - return spatial; -} - -void EditorSceneImporterGLTF::_generate_scene_node(GLTFState &state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) { - const GLTFNode *gltf_node = state.nodes[node_index]; - - if (gltf_node->skeleton >= 0) { - _generate_skeleton_bone_node(state, scene_parent, scene_root, node_index); - return; - } - - Spatial *current_node = nullptr; - - // Is our parent a skeleton - Skeleton *active_skeleton = Object::cast_to(scene_parent); - - const bool non_bone_parented_to_skeleton = active_skeleton; - - // skinned meshes must not be placed in a bone attachment. - if (non_bone_parented_to_skeleton && gltf_node->skin < 0) { - // Bone Attachment - Parent Case - BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, gltf_node->parent); - - scene_parent->add_child(bone_attachment); - bone_attachment->set_owner(scene_root); - - // There is no gltf_node that represent this, so just directly create a unique name - bone_attachment->set_name(_gen_unique_name(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; - } - - // We still have not managed to make a node - if (gltf_node->mesh >= 0) { - current_node = _generate_mesh_instance(state, scene_parent, node_index); - } else if (gltf_node->camera >= 0) { - current_node = _generate_camera(state, scene_parent, node_index); - } else if (gltf_node->light >= 0) { - current_node = _generate_light(state, scene_parent, node_index); - } else { - current_node = _generate_spatial(state, scene_parent, node_index); - } - - scene_parent->add_child(current_node); - current_node->set_owner(scene_root); - current_node->set_transform(gltf_node->xform); - if (state.use_legacy_names) { - current_node->set_name(_legacy_validate_node_name(gltf_node->name)); - } else { - current_node->set_name(gltf_node->name); - } - - state.scene_nodes.insert(node_index, current_node); - - for (int i = 0; i < gltf_node->children.size(); ++i) { - _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]); - } -} - -void EditorSceneImporterGLTF::_generate_skeleton_bone_node(GLTFState &state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) { - const GLTFNode *gltf_node = state.nodes[node_index]; - - Spatial *current_node = nullptr; - - Skeleton *skeleton = state.skeletons[gltf_node->skeleton].godot_skeleton; - // In this case, this node is already a bone in skeleton. - 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); - if (active_skeleton != skeleton) { - if (active_skeleton) { - // Bone Attachment - Direct Parented Skeleton Case - BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, gltf_node->parent); - - scene_parent->add_child(bone_attachment); - bone_attachment->set_owner(scene_root); - - // There is no gltf_node that represent this, so just directly create a unique name - bone_attachment->set_name(_gen_unique_name(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)); - } - - // 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); - } - } - - active_skeleton = skeleton; - current_node = skeleton; - - // If we have an active skeleton, and the node is node skinned, we need to create a bone attachment - if (requires_extra_node) { - // skinned meshes must not be placed in a bone attachment. - if (!is_skinned_mesh) { - BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, node_index); - - scene_parent->add_child(bone_attachment); - bone_attachment->set_owner(scene_root); - - // There is no gltf_node that represent this, so just directly create a unique name - bone_attachment->set_name(_gen_unique_name(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; - current_node = nullptr; - } - - // We still have not managed to make a node - if (gltf_node->mesh >= 0) { - current_node = _generate_mesh_instance(state, scene_parent, node_index); - } else if (gltf_node->camera >= 0) { - current_node = _generate_camera(state, scene_parent, node_index); - } else if (gltf_node->light >= 0) { - current_node = _generate_light(state, scene_parent, node_index); - } - - scene_parent->add_child(current_node); - current_node->set_owner(scene_root); - // Do not set transform here. Transform is already applied to our bone. - if (state.use_legacy_names) { - current_node->set_name(_legacy_validate_node_name(gltf_node->name)); - } else { - current_node->set_name(gltf_node->name); - } - } - - state.scene_nodes.insert(node_index, current_node); - - for (int i = 0; i < gltf_node->children.size(); ++i) { - _generate_scene_node(state, active_skeleton, scene_root, gltf_node->children[i]); - } -} - -template -struct EditorSceneImporterGLTFInterpolate { - T lerp(const T &a, const T &b, float c) const { - return a + (b - a) * c; - } - - T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) { - const float t2 = t * t; - const float t3 = t2 * t; - - return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); - } - - T bezier(T start, T control_1, T control_2, T end, float t) { - /* Formula from Wikipedia article on Bezier curves. */ - const real_t omt = (1.0 - t); - const real_t omt2 = omt * omt; - const real_t omt3 = omt2 * omt; - const real_t t2 = t * t; - const real_t t3 = t2 * t; - - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; - } -}; - -// thank you for existing, partial specialization -template <> -struct EditorSceneImporterGLTFInterpolate { - Quat lerp(const Quat &a, const Quat &b, const float c) const { - ERR_FAIL_COND_V_MSG(!a.is_normalized(), Quat(), "The quaternion \"a\" must be normalized."); - ERR_FAIL_COND_V_MSG(!b.is_normalized(), Quat(), "The quaternion \"b\" must be normalized."); - - return a.slerp(b, c).normalized(); - } - - Quat catmull_rom(const Quat &p0, const Quat &p1, const Quat &p2, const Quat &p3, const float c) { - ERR_FAIL_COND_V_MSG(!p1.is_normalized(), Quat(), "The quaternion \"p1\" must be normalized."); - ERR_FAIL_COND_V_MSG(!p2.is_normalized(), Quat(), "The quaternion \"p2\" must be normalized."); - - return p1.slerp(p2, c).normalized(); - } - - Quat bezier(const Quat start, const Quat control_1, const Quat control_2, const Quat end, const float t) { - ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quat(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quat(), "The end quaternion must be normalized."); - - return start.slerp(end, t).normalized(); - } -}; - -template -T EditorSceneImporterGLTF::_interpolate_track(const Vector &p_times, const Vector &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { - //could use binary search, worth it? - int idx = -1; - for (int i = 0; i < p_times.size(); i++) { - if (p_times[i] > p_time) { - break; - } - idx++; - } - - EditorSceneImporterGLTFInterpolate interp; - - switch (p_interp) { - case GLTFAnimation::INTERP_LINEAR: { - if (idx == -1) { - return p_values[0]; - } else if (idx >= p_times.size() - 1) { - return p_values[p_times.size() - 1]; - } - - const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - return interp.lerp(p_values[idx], p_values[idx + 1], c); - - } break; - case GLTFAnimation::INTERP_STEP: { - if (idx == -1) { - return p_values[0]; - } else if (idx >= p_times.size() - 1) { - return p_values[p_times.size() - 1]; - } - - return p_values[idx]; - - } break; - case GLTFAnimation::INTERP_CATMULLROMSPLINE: { - if (idx == -1) { - return p_values[1]; - } else if (idx >= p_times.size() - 1) { - return p_values[1 + p_times.size() - 1]; - } - - const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - return interp.catmull_rom(p_values[idx - 1], p_values[idx], p_values[idx + 1], p_values[idx + 3], c); - - } break; - case GLTFAnimation::INTERP_CUBIC_SPLINE: { - if (idx == -1) { - return p_values[1]; - } else if (idx >= p_times.size() - 1) { - return p_values[(p_times.size() - 1) * 3 + 1]; - } - - const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - const T from = p_values[idx * 3 + 1]; - const T c1 = from + p_values[idx * 3 + 2]; - const T to = p_values[idx * 3 + 4]; - const T c2 = to + p_values[idx * 3 + 3]; - - return interp.bezier(from, c1, c2, to, c); - - } break; - } - - ERR_FAIL_V(p_values[0]); -} - -void EditorSceneImporterGLTF::_import_animation(GLTFState &state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) { - const GLTFAnimation &anim = state.animations[index]; - - String name = anim.name; - if (name.empty()) { - // No node represent these, and they are not in the hierarchy, so just make a unique name - name = _gen_unique_name(state, "Animation"); - } - - Ref animation; - animation.instance(); - animation->set_name(name); - - if (anim.loop) { - animation->set_loop(true); - } - - float length = 0; - - for (Map::Element *E = anim.tracks.front(); E; E = E->next()) { - const GLTFAnimation::Track &track = E->get(); - //need to find the path: for skeletons, weight tracks will affect the mesh - NodePath node_path; - //for skeletons, transform tracks always affect bones - NodePath transform_node_path; - - GLTFNodeIndex node_index = E->key(); - - const GLTFNode *node = state.nodes[E->key()]; - - Node *root = ap->get_parent(); - ERR_FAIL_COND(root == nullptr); - Map::Element *node_element = state.scene_nodes.find(node_index); - ERR_CONTINUE_MSG(node_element == nullptr, vformat("Unable to find node %d for animation", node_index)); - node_path = root->get_path_to(node_element->get()); - - if (node->skeleton >= 0) { - const Skeleton *sk = state.skeletons[node->skeleton].godot_skeleton; - ERR_FAIL_COND(sk == nullptr); - - const String path = ap->get_parent()->get_path_to(sk); - const String bone = node->name; - transform_node_path = path + ":" + bone; - } else { - transform_node_path = node_path; - } - - for (int i = 0; i < track.rotation_track.times.size(); i++) { - length = MAX(length, track.rotation_track.times[i]); - } - for (int i = 0; i < track.translation_track.times.size(); i++) { - length = MAX(length, track.translation_track.times[i]); - } - for (int i = 0; i < track.scale_track.times.size(); i++) { - length = MAX(length, track.scale_track.times[i]); - } - - for (int i = 0; i < track.weight_tracks.size(); i++) { - for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { - length = MAX(length, track.weight_tracks[i].times[j]); - } - } - - // Animated TRS properties will not affect a skinned mesh. - const bool transform_affects_skinned_mesh_instance = node->skeleton < 0 && node->skin >= 0; - if ((track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) && !transform_affects_skinned_mesh_instance) { - //make transform track - int track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_TRANSFORM); - animation->track_set_path(track_idx, transform_node_path); - animation->track_set_imported(track_idx, true); - //first determine animation length - - const double increment = 1.0 / bake_fps; - double time = 0.0; - - Vector3 base_pos; - Quat base_rot; - Vector3 base_scale = Vector3(1, 1, 1); - - if (!track.rotation_track.values.size()) { - base_rot = state.nodes[E->key()]->rotation.normalized(); - } - - if (!track.translation_track.values.size()) { - base_pos = state.nodes[E->key()]->translation; - } - - if (!track.scale_track.values.size()) { - base_scale = state.nodes[E->key()]->scale; - } - - bool last = false; - while (true) { - Vector3 pos = base_pos; - Quat rot = base_rot; - Vector3 scale = base_scale; - - if (track.translation_track.times.size()) { - pos = _interpolate_track(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation); - } - - if (track.rotation_track.times.size()) { - rot = _interpolate_track(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); - } - - if (track.scale_track.times.size()) { - scale = _interpolate_track(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); - } - - if (node->skeleton >= 0) { - Transform xform; - xform.basis.set_quat_scale(rot, scale); - xform.origin = pos; - - const Skeleton *skeleton = state.skeletons[node->skeleton].godot_skeleton; - const int bone_idx = skeleton->find_bone(node->name); - xform = skeleton->get_bone_rest(bone_idx).affine_inverse() * xform; - - rot = xform.basis.get_rotation_quat(); - rot.normalize(); - scale = xform.basis.get_scale(); - pos = xform.origin; - } - - animation->transform_track_insert_key(track_idx, time, pos, rot, scale); - - if (last) { - break; - } - time += increment; - if (time >= length) { - last = true; - time = length; - } - } - } - - for (int i = 0; i < track.weight_tracks.size(); i++) { - ERR_CONTINUE(node->mesh < 0 || node->mesh >= state.meshes.size()); - const GLTFMesh &mesh = state.meshes[node->mesh]; - const String prop = "blend_shapes/" + mesh.mesh->get_blend_shape_name(i); - - const String blend_path = String(node_path) + ":" + prop; - - const int track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_VALUE); - animation->track_set_path(track_idx, blend_path); - - // Only LINEAR and STEP (NEAREST) can be supported out of the box by Godot's Animation, - // the other modes have to be baked. - GLTFAnimation::Interpolation gltf_interp = track.weight_tracks[i].interpolation; - if (gltf_interp == GLTFAnimation::INTERP_LINEAR || gltf_interp == GLTFAnimation::INTERP_STEP) { - animation->track_set_interpolation_type(track_idx, gltf_interp == GLTFAnimation::INTERP_STEP ? Animation::INTERPOLATION_NEAREST : Animation::INTERPOLATION_LINEAR); - for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { - const float t = track.weight_tracks[i].times[j]; - const float w = track.weight_tracks[i].values[j]; - animation->track_insert_key(track_idx, t, w); - } - } else { - // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / bake_fps; - double time = 0.0; - bool last = false; - while (true) { - _interpolate_track(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); - if (last) { - break; - } - time += increment; - if (time >= length) { - last = true; - time = length; - } - } - } - } - } - - animation->set_length(length); - - ap->add_animation(name, animation); -} - -void EditorSceneImporterGLTF::_process_mesh_instances(GLTFState &state, Spatial *scene_root) { - for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { - const GLTFNode *node = state.nodes[node_i]; - - if (node->skin >= 0 && node->mesh >= 0) { - const GLTFSkinIndex skin_i = node->skin; - - Map::Element *mi_element = state.scene_nodes.find(node_i); - ERR_CONTINUE_MSG(mi_element == nullptr, vformat("Unable to find node %d", node_i)); - - MeshInstance *mi = Object::cast_to(mi_element->get()); - ERR_CONTINUE_MSG(mi == nullptr, vformat("Unable to cast node %d of type %s to MeshInstance", node_i, mi_element->get()->get_class_name())); - - const GLTFSkeletonIndex skel_i = state.skins[node->skin].skeleton; - const GLTFSkeleton &gltf_skeleton = state.skeletons[skel_i]; - Skeleton *skeleton = gltf_skeleton.godot_skeleton; - ERR_CONTINUE_MSG(skeleton == nullptr, vformat("Unable to find Skeleton for node %d skin %d", node_i, skin_i)); - - mi->get_parent()->remove_child(mi); - skeleton->add_child(mi); - mi->set_owner(scene_root); - - mi->set_skin(state.skins[skin_i].godot_skin); - mi->set_skeleton_path(mi->get_path_to(skeleton)); - mi->set_transform(Transform()); - } - } -} - -Spatial *EditorSceneImporterGLTF::_generate_scene(GLTFState &state, const int p_bake_fps) { - Spatial *root = memnew(Spatial); - - // scene_name is already unique - if (state.use_legacy_names) { - root->set_name(_legacy_validate_node_name(state.scene_name)); - } else { - root->set_name(state.scene_name); - } - - for (int i = 0; i < state.root_nodes.size(); ++i) { - _generate_scene_node(state, root, root, state.root_nodes[i]); - } - - _process_mesh_instances(state, root); - - if (state.animations.size()) { - AnimationPlayer *ap = memnew(AnimationPlayer); - ap->set_name("AnimationPlayer"); - root->add_child(ap); - ap->set_owner(root); - - for (int i = 0; i < state.animations.size(); i++) { - _import_animation(state, ap, i, p_bake_fps); - } - } - - return root; -} - -Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err) { - print_verbose(vformat("glTF: Importing file %s as scene.", p_path)); - - GLTFState state; - - if (p_path.to_lower().ends_with("glb")) { - //binary file - //text file - Error err = _parse_glb(p_path, state); - if (err) { - return nullptr; - } - } else { - //text file - Error err = _parse_json(p_path, state); - if (err) { - return nullptr; - } - } - - ERR_FAIL_COND_V(!state.json.has("asset"), nullptr); - - Dictionary asset = state.json["asset"]; - - ERR_FAIL_COND_V(!asset.has("version"), nullptr); - - String version = asset["version"]; - - state.import_flags = p_flags; - state.major_version = version.get_slice(".", 0).to_int(); - state.minor_version = version.get_slice(".", 1).to_int(); - state.use_named_skin_binds = p_flags & IMPORT_USE_NAMED_SKIN_BINDS; - state.use_legacy_names = p_flags & IMPORT_USE_LEGACY_NAMES; - - /* STEP 0 PARSE SCENE */ - Error err = _parse_scenes(state); - if (err != OK) { - return nullptr; - } - - /* STEP 1 PARSE NODES */ - err = _parse_nodes(state); - if (err != OK) { - return nullptr; - } - - /* STEP 2 PARSE BUFFERS */ - err = _parse_buffers(state, p_path.get_base_dir()); - if (err != OK) { - return nullptr; - } - - /* STEP 3 PARSE BUFFER VIEWS */ - err = _parse_buffer_views(state); - if (err != OK) { - return nullptr; - } - - /* STEP 4 PARSE ACCESSORS */ - err = _parse_accessors(state); - if (err != OK) { - return nullptr; - } - - /* STEP 5 PARSE IMAGES */ - err = _parse_images(state, p_path.get_base_dir()); - if (err != OK) { - return nullptr; - } - - /* STEP 6 PARSE TEXTURES */ - err = _parse_textures(state); - if (err != OK) { - return nullptr; - } - - /* STEP 7 PARSE TEXTURES */ - err = _parse_materials(state); - if (err != OK) { - return nullptr; - } - - /* STEP 9 PARSE SKINS */ - err = _parse_skins(state); - if (err != OK) { - return nullptr; - } - - /* STEP 10 DETERMINE SKELETONS */ - err = _determine_skeletons(state); - if (err != OK) { - return nullptr; - } - - /* STEP 11 CREATE SKELETONS */ - err = _create_skeletons(state); - if (err != OK) { - return nullptr; - } - - /* STEP 12 CREATE SKINS */ - err = _create_skins(state); - if (err != OK) { - return nullptr; - } - - /* STEP 13 PARSE MESHES (we have enough info now) */ - err = _parse_meshes(state); - if (err != OK) { - return nullptr; - } - - /* STEP 14 PARSE LIGHTS */ - err = _parse_lights(state); - if (err != OK) { - return nullptr; - } - - /* STEP 15 PARSE CAMERAS */ - err = _parse_cameras(state); - if (err != OK) { - return nullptr; - } - - /* STEP 16 PARSE ANIMATIONS */ - err = _parse_animations(state); - if (err != OK) { - return nullptr; - } - - /* STEP 17 ASSIGN SCENE NAMES */ - _assign_scene_names(state); - - /* STEP 18 MAKE SCENE! */ - Spatial *scene = _generate_scene(state, p_bake_fps); - - return scene; -} - -Ref EditorSceneImporterGLTF::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { - return Ref(); -} - -EditorSceneImporterGLTF::EditorSceneImporterGLTF() { -} diff --git a/editor/import/editor_scene_importer_gltf.h b/editor/import/editor_scene_importer_gltf.h deleted file mode 100644 index ca04f622a6..0000000000 --- a/editor/import/editor_scene_importer_gltf.h +++ /dev/null @@ -1,447 +0,0 @@ -/*************************************************************************/ -/* editor_scene_importer_gltf.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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 EDITOR_SCENE_IMPORTER_GLTF_H -#define EDITOR_SCENE_IMPORTER_GLTF_H - -#include "editor/import/resource_importer_scene.h" -#include "scene/3d/light.h" -#include "scene/3d/skeleton.h" -#include "scene/3d/spatial.h" - -class AnimationPlayer; -class BoneAttachment; -class MeshInstance; - -class EditorSceneImporterGLTF : public EditorSceneImporter { - GDCLASS(EditorSceneImporterGLTF, EditorSceneImporter); - - typedef int GLTFAccessorIndex; - typedef int GLTFAnimationIndex; - typedef int GLTFBufferIndex; - typedef int GLTFBufferViewIndex; - typedef int GLTFCameraIndex; - typedef int GLTFImageIndex; - typedef int GLTFMaterialIndex; - typedef int GLTFMeshIndex; - typedef int GLTFLightIndex; - typedef int GLTFNodeIndex; - typedef int GLTFSkeletonIndex; - typedef int GLTFSkinIndex; - typedef int GLTFTextureIndex; - - enum { - ARRAY_BUFFER = 34962, - ELEMENT_ARRAY_BUFFER = 34963, - - TYPE_BYTE = 5120, - TYPE_UNSIGNED_BYTE = 5121, - TYPE_SHORT = 5122, - TYPE_UNSIGNED_SHORT = 5123, - TYPE_UNSIGNED_INT = 5125, - TYPE_FLOAT = 5126, - - COMPONENT_TYPE_BYTE = 5120, - COMPONENT_TYPE_UNSIGNED_BYTE = 5121, - COMPONENT_TYPE_SHORT = 5122, - COMPONENT_TYPE_UNSIGNED_SHORT = 5123, - COMPONENT_TYPE_INT = 5125, - COMPONENT_TYPE_FLOAT = 5126, - - }; - - String _get_component_type_name(const uint32_t p_component); - int _get_component_type_size(const int component_type); - - enum GLTFType { - TYPE_SCALAR, - TYPE_VEC2, - TYPE_VEC3, - TYPE_VEC4, - TYPE_MAT2, - TYPE_MAT3, - TYPE_MAT4, - }; - - String _get_type_name(const GLTFType p_component); - - struct GLTFNode { - //matrices need to be transformed to this - GLTFNodeIndex parent; - int height; - - Transform xform; - String name; - - GLTFMeshIndex mesh; - GLTFCameraIndex camera; - GLTFSkinIndex skin; - - GLTFSkeletonIndex skeleton; - bool joint; - - Vector3 translation; - Quat rotation; - Vector3 scale; - - Vector children; - - GLTFLightIndex light; - - GLTFNode() : - parent(-1), - height(-1), - mesh(-1), - camera(-1), - skin(-1), - skeleton(-1), - joint(false), - translation(0, 0, 0), - scale(Vector3(1, 1, 1)), - light(-1) {} - }; - - struct GLTFBufferView { - GLTFBufferIndex buffer; - int byte_offset; - int byte_length; - int byte_stride; - bool indices; - //matrices need to be transformed to this - - GLTFBufferView() : - buffer(-1), - byte_offset(0), - byte_length(0), - byte_stride(0), - indices(false) { - } - }; - - struct GLTFAccessor { - GLTFBufferViewIndex buffer_view; - int byte_offset; - int component_type; - bool normalized; - int count; - GLTFType type; - float min; - float max; - int sparse_count; - int sparse_indices_buffer_view; - int sparse_indices_byte_offset; - int sparse_indices_component_type; - int sparse_values_buffer_view; - int sparse_values_byte_offset; - - GLTFAccessor() { - buffer_view = 0; - byte_offset = 0; - component_type = 0; - normalized = false; - count = 0; - min = 0; - max = 0; - sparse_count = 0; - sparse_indices_buffer_view = 0; - sparse_indices_byte_offset = 0; - sparse_indices_component_type = 0; - sparse_values_buffer_view = 0; - sparse_values_byte_offset = 0; - } - }; - struct GLTFTexture { - GLTFImageIndex src_image; - }; - - struct GLTFSkeleton { - // The *synthesized* skeletons joints - Vector joints; - - // The roots of the skeleton. If there are multiple, each root must have the same parent - // (ie roots are siblings) - Vector roots; - - // The created Skeleton for the scene - Skeleton *godot_skeleton; - - // Set of unique bone names for the skeleton - Set unique_names; - - GLTFSkeleton() : - godot_skeleton(nullptr) { - } - }; - - struct GLTFSkin { - String name; - - // The "skeleton" property defined in the gltf spec. -1 = Scene Root - GLTFNodeIndex skin_root; - - Vector joints_original; - Vector inverse_binds; - - // Note: joints + non_joints should form a complete subtree, or subtrees with a common parent - - // All nodes that are skins that are caught in-between the original joints - // (inclusive of joints_original) - Vector joints; - - // All Nodes that are caught in-between skin joint nodes, and are not defined - // as joints by any skin - Vector non_joints; - - // The roots of the skin. In the case of multiple roots, their parent *must* - // be the same (the roots must be siblings) - Vector roots; - - // The GLTF Skeleton this Skin points to (after we determine skeletons) - GLTFSkeletonIndex skeleton; - - // A mapping from the joint indices (in the order of joints_original) to the - // Godot Skeleton's bone_indices - Map joint_i_to_bone_i; - Map joint_i_to_name; - - // The Actual Skin that will be created as a mapping between the IBM's of this skin - // to the generated skeleton for the mesh instances. - Ref godot_skin; - - GLTFSkin() : - skin_root(-1), - skeleton(-1) {} - }; - - struct GLTFMesh { - Ref mesh; - Vector blend_weights; - }; - - struct GLTFCamera { - bool perspective; - float fov_size; - float zfar; - float znear; - - GLTFCamera() { - perspective = true; - fov_size = 65; - zfar = 500; - znear = 0.1; - } - }; - - struct GLTFLight { - Color color; - float intensity; - String type; - float range; - float inner_cone_angle; - float outer_cone_angle; - GLTFLight() { - color = Color(1.0f, 1.0f, 1.0f); - intensity = 1.0f; - type = ""; - range = Math_INF; - inner_cone_angle = 0.0f; - outer_cone_angle = Math_PI / 4.0; - } - }; - - struct GLTFAnimation { - bool loop = false; - - enum Interpolation { - INTERP_LINEAR, - INTERP_STEP, - INTERP_CATMULLROMSPLINE, - INTERP_CUBIC_SPLINE - }; - - template - struct Channel { - Interpolation interpolation; - Vector times; - Vector values; - }; - - struct Track { - Channel translation_track; - Channel rotation_track; - Channel scale_track; - Vector> weight_tracks; - }; - - String name; - - Map tracks; - }; - - struct GLTFState { - Dictionary json; - int major_version; - int minor_version; - Vector glb_data; - - bool use_named_skin_binds; - bool use_legacy_names; - - Vector nodes; - Vector> buffers; - Vector buffer_views; - Vector accessors; - - Vector meshes; //meshes are loaded directly, no reason not to. - Vector> materials; - - String scene_name; - Vector root_nodes; - - Vector textures; - Vector> images; - - Vector skins; - Vector cameras; - Vector lights; - - Set unique_names; - Set unique_animation_names; - - Vector skeletons; - Vector animations; - - Map scene_nodes; - - // EditorSceneImporter::ImportFlags - uint32_t import_flags; - - ~GLTFState() { - for (int i = 0; i < nodes.size(); i++) { - memdelete(nodes[i]); - } - } - }; - - String _sanitize_scene_name(GLTFState &state, const String &p_name); - String _legacy_validate_node_name(const String &p_name); - String _gen_unique_name(GLTFState &state, const String &p_name); - - String _sanitize_animation_name(const String &p_name); - String _gen_unique_animation_name(GLTFState &state, const String &p_name); - - String _sanitize_bone_name(GLTFState &state, const String &p_name); - String _gen_unique_bone_name(GLTFState &state, const GLTFSkeletonIndex skel_i, const String &p_name); - - Ref _get_texture(GLTFState &state, const GLTFTextureIndex p_texture); - - Error _parse_json(const String &p_path, GLTFState &state); - Error _parse_glb(const String &p_path, GLTFState &state); - - Error _parse_scenes(GLTFState &state); - Error _parse_nodes(GLTFState &state); - - void _compute_node_heights(GLTFState &state); - - Error _parse_buffers(GLTFState &state, const String &p_base_path); - Error _parse_buffer_views(GLTFState &state); - GLTFType _get_type_from_str(const String &p_string); - Error _parse_accessors(GLTFState &state); - Error _decode_buffer_view(GLTFState &state, double *dst, const GLTFBufferViewIndex p_buffer_view, const int skip_every, const int skip_bytes, const int element_size, const int count, const GLTFType type, const int component_count, const int component_type, const int component_size, const bool normalized, const int byte_offset, const bool for_vertex); - - Vector _decode_accessor(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - PoolVector _decode_accessor_as_floats(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - PoolVector _decode_accessor_as_ints(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - PoolVector _decode_accessor_as_vec2(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - PoolVector _decode_accessor_as_vec3(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - PoolVector _decode_accessor_as_color(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector _decode_accessor_as_quat(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector _decode_accessor_as_xform2d(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector _decode_accessor_as_basis(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector _decode_accessor_as_xform(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - - Error _parse_meshes(GLTFState &state); - Error _parse_images(GLTFState &state, const String &p_base_path); - Error _parse_textures(GLTFState &state); - - Error _parse_materials(GLTFState &state); - - GLTFNodeIndex _find_highest_node(GLTFState &state, const Vector &subset); - - bool _capture_nodes_in_skin(GLTFState &state, GLTFSkin &skin, const GLTFNodeIndex node_index); - void _capture_nodes_for_multirooted_skin(GLTFState &state, GLTFSkin &skin); - Error _expand_skin(GLTFState &state, GLTFSkin &skin); - Error _verify_skin(GLTFState &state, GLTFSkin &skin); - Error _parse_skins(GLTFState &state); - - Error _determine_skeletons(GLTFState &state); - Error _reparent_non_joint_skeleton_subtrees(GLTFState &state, GLTFSkeleton &skeleton, const Vector &non_joints); - Error _determine_skeleton_roots(GLTFState &state, const GLTFSkeletonIndex skel_i); - - Error _create_skeletons(GLTFState &state); - Error _map_skin_joints_indices_to_skeleton_bone_indices(GLTFState &state); - - Error _create_skins(GLTFState &state); - bool _skins_are_same(const Ref &skin_a, const Ref &skin_b); - void _remove_duplicate_skins(GLTFState &state); - - Error _parse_cameras(GLTFState &state); - Error _parse_lights(GLTFState &state); - Error _parse_animations(GLTFState &state); - - BoneAttachment *_generate_bone_attachment(GLTFState &state, Skeleton *skeleton, const GLTFNodeIndex node_index, const GLTFNodeIndex bone_index); - MeshInstance *_generate_mesh_instance(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); - Camera *_generate_camera(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); - Spatial *_generate_light(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); - Spatial *_generate_spatial(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); - - void _generate_scene_node(GLTFState &state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index); - void _generate_skeleton_bone_node(GLTFState &state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index); - Spatial *_generate_scene(GLTFState &state, const int p_bake_fps); - - void _process_mesh_instances(GLTFState &state, Spatial *scene_root); - - void _assign_scene_names(GLTFState &state); - - template - T _interpolate_track(const Vector &p_times, const Vector &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp); - - void _import_animation(GLTFState &state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps); - -public: - virtual uint32_t get_import_flags() const; - virtual void get_extensions(List *r_extensions) const; - virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps = nullptr, Error *r_err = nullptr); - virtual Ref import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps); - - EditorSceneImporterGLTF(); -}; - -#endif // EDITOR_SCENE_IMPORTER_GLTF_H diff --git a/modules/gltf/SCsub b/modules/gltf/SCsub new file mode 100644 index 0000000000..5d03ee8361 --- /dev/null +++ b/modules/gltf/SCsub @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_gltf = env_modules.Clone() +env_gltf.Prepend(CPPPATH=["."]) + +# Godot's own source files +env_gltf.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/gltf/config.py b/modules/gltf/config.py new file mode 100644 index 0000000000..a4ee871eff --- /dev/null +++ b/modules/gltf/config.py @@ -0,0 +1,30 @@ +def can_build(env, platform): + return env["tools"] and not env["disable_3d"] + + +def configure(env): + pass + + +def get_doc_classes(): + return [ + "EditorSceneImporterGLTF", + "GLTFAccessor", + "GLTFAnimation", + "GLTFBufferView", + "GLTFCamera", + "GLTFDocument", + "GLTFLight", + "GLTFMesh", + "GLTFNode", + "GLTFSkeleton", + "GLTFSkin", + "GLTFSpecGloss", + "GLTFState", + "GLTFTexture", + "PackedSceneGLTF", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/gltf/doc_classes/EditorSceneImporterGLTF.xml b/modules/gltf/doc_classes/EditorSceneImporterGLTF.xml new file mode 100644 index 0000000000..d1399ff0eb --- /dev/null +++ b/modules/gltf/doc_classes/EditorSceneImporterGLTF.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml new file mode 100644 index 0000000000..cb6662b902 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFAccessor.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFAnimation.xml b/modules/gltf/doc_classes/GLTFAnimation.xml new file mode 100644 index 0000000000..1af55ec94f --- /dev/null +++ b/modules/gltf/doc_classes/GLTFAnimation.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml new file mode 100644 index 0000000000..df8aaf3d5f --- /dev/null +++ b/modules/gltf/doc_classes/GLTFBufferView.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml new file mode 100644 index 0000000000..d0b9d4fbd1 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFCamera.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml new file mode 100644 index 0000000000..68b10cc92f --- /dev/null +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml new file mode 100644 index 0000000000..04a45881a0 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFLight.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFMesh.xml b/modules/gltf/doc_classes/GLTFMesh.xml new file mode 100644 index 0000000000..8d86eefbd8 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFMesh.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml new file mode 100644 index 0000000000..d347fb5daf --- /dev/null +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFSkeleton.xml b/modules/gltf/doc_classes/GLTFSkeleton.xml new file mode 100644 index 0000000000..801e48748d --- /dev/null +++ b/modules/gltf/doc_classes/GLTFSkeleton.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFSkin.xml b/modules/gltf/doc_classes/GLTFSkin.xml new file mode 100644 index 0000000000..5f7ce097d9 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFSkin.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFSpecGloss.xml b/modules/gltf/doc_classes/GLTFSpecGloss.xml new file mode 100644 index 0000000000..5fc1942ed8 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFSpecGloss.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml new file mode 100644 index 0000000000..d6192c1e5c --- /dev/null +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml new file mode 100644 index 0000000000..dc4273b1bc --- /dev/null +++ b/modules/gltf/doc_classes/GLTFTexture.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/doc_classes/PackedSceneGLTF.xml b/modules/gltf/doc_classes/PackedSceneGLTF.xml new file mode 100644 index 0000000000..451496bebf --- /dev/null +++ b/modules/gltf/doc_classes/PackedSceneGLTF.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp new file mode 100644 index 0000000000..62c8f7b08f --- /dev/null +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* editor_scene_exporter_gltf_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "editor_scene_exporter_gltf_plugin.h" +#include "core/object.h" +#include "core/project_settings.h" +#include "core/vector.h" +#include "editor/editor_file_system.h" +#include "scene/3d/mesh_instance.h" +#include "scene/gui/check_box.h" +#include "scene/main/node.h" + +#include "editor/editor_node.h" + +String SceneExporterGLTFPlugin::get_name() const { + return "ConvertGLTF2"; +} + +bool SceneExporterGLTFPlugin::has_main_screen() const { + return false; +} + +SceneExporterGLTFPlugin::SceneExporterGLTFPlugin(EditorNode *p_node) { + editor = p_node; + convert_gltf2.instance(); + file_export_lib = memnew(EditorFileDialog); + editor->get_gui_base()->add_child(file_export_lib); + file_export_lib->connect("file_selected", this, "_gltf2_dialog_action"); + file_export_lib->set_title(TTR("Export Library")); + file_export_lib->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_export_lib->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file_export_lib->clear_filters(); + file_export_lib->add_filter("*.glb"); + file_export_lib->add_filter("*.gltf"); + file_export_lib->set_title(TTR("Export Mesh GLTF2")); + String gltf_scene_name = TTR("Export GLTF..."); + add_tool_menu_item(gltf_scene_name, this, "convert_scene_to_gltf2", DEFVAL(Variant())); +} + +void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) { + Node *root = editor->get_tree()->get_edited_scene_root(); + if (!root) { + editor->show_accept(TTR("This operation can't be done without a scene."), TTR("OK")); + return; + } + List deps; + convert_gltf2->save_scene(root, p_file, p_file, 0, 1000.0f, &deps); + EditorFileSystem::get_singleton()->scan_changes(); +} + +void SceneExporterGLTFPlugin::_bind_methods() { + ClassDB::bind_method(D_METHOD("convert_scene_to_gltf2"), &SceneExporterGLTFPlugin::convert_scene_to_gltf2); + ClassDB::bind_method(D_METHOD("_gltf2_dialog_action", "file"), &SceneExporterGLTFPlugin::_gltf2_dialog_action); +} + +void SceneExporterGLTFPlugin::convert_scene_to_gltf2(Variant p_null) { + Node *root = editor->get_tree()->get_edited_scene_root(); + if (!root) { + editor->show_accept(TTR("This operation can't be done without a scene."), TTR("OK")); + return; + } + String filename = String(root->get_filename().get_file().get_basename()); + if (filename.empty()) { + filename = root->get_name(); + } + file_export_lib->set_current_file(filename + String(".gltf")); + file_export_lib->popup_centered_ratio(); +} diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor_scene_exporter_gltf_plugin.h new file mode 100644 index 0000000000..00e14baa36 --- /dev/null +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.h @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* editor_scene_exporter_gltf_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 EDITOR_SCENE_EXPORTER_GLTF_PLUGIN_H +#define EDITOR_SCENE_EXPORTER_GLTF_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "editor_scene_importer_gltf.h" + +class SceneExporterGLTFPlugin : public EditorPlugin { + GDCLASS(SceneExporterGLTFPlugin, EditorPlugin); + + Ref convert_gltf2; + EditorNode *editor = nullptr; + EditorFileDialog *file_export_lib = nullptr; + void _gltf2_dialog_action(String p_file); + void convert_scene_to_gltf2(Variant p_null); + +protected: + static void _bind_methods(); + +public: + virtual String get_name() const; + bool has_main_screen() const; + SceneExporterGLTFPlugin(class EditorNode *p_node); +}; + +#endif // EDITOR_SCENE_EXPORTER_GLTF_PLUGIN_H diff --git a/modules/gltf/editor_scene_importer_gltf.cpp b/modules/gltf/editor_scene_importer_gltf.cpp new file mode 100644 index 0000000000..cd31a6ca65 --- /dev/null +++ b/modules/gltf/editor_scene_importer_gltf.cpp @@ -0,0 +1,187 @@ +/*************************************************************************/ +/* editor_scene_importer_gltf.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "core/crypto/crypto_core.h" +#include "core/io/json.h" +#include "core/math/disjoint_set.h" +#include "core/math/math_defs.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "editor/import/resource_importer_scene.h" +#include "modules/gltf/gltf_state.h" +#include "modules/regex/regex.h" +#include "scene/3d/bone_attachment.h" +#include "scene/3d/camera.h" +#include "scene/3d/mesh_instance.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/packed_scene.h" +#include "scene/resources/surface_tool.h" + +#include "modules/gltf/editor_scene_importer_gltf.h" + +uint32_t EditorSceneImporterGLTF::get_import_flags() const { + return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; +} + +void EditorSceneImporterGLTF::get_extensions(List *r_extensions) const { + r_extensions->push_back("gltf"); + r_extensions->push_back("glb"); +} + +Node *EditorSceneImporterGLTF::import_scene(const String &p_path, + uint32_t p_flags, int p_bake_fps, + List *r_missing_deps, + Error *r_err) { + Ref importer; + importer.instance(); + return importer->import_scene(p_path, p_flags, p_bake_fps, r_missing_deps, r_err, Ref()); +} + +Ref EditorSceneImporterGLTF::import_animation(const String &p_path, + uint32_t p_flags, + int p_bake_fps) { + return Ref(); +} + +void PackedSceneGLTF::_bind_methods() { + ClassDB::bind_method( + D_METHOD("export_gltf", "node", "path", "flags", "bake_fps"), + &PackedSceneGLTF::export_gltf, DEFVAL(0), DEFVAL(1000.0f)); + ClassDB::bind_method(D_METHOD("pack_gltf", "path", "flags", "bake_fps", "state"), + &PackedSceneGLTF::pack_gltf, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("import_gltf_scene", "path", "flags", "bake_fps", "state"), + &PackedSceneGLTF::import_gltf_scene, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref())); +} +Node *PackedSceneGLTF::import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref r_state) { + Error err = FAILED; + List deps; + return import_scene(p_path, p_flags, p_bake_fps, &deps, &err, r_state); +} + +Node *PackedSceneGLTF::import_scene(const String &p_path, uint32_t p_flags, + int p_bake_fps, + List *r_missing_deps, + Error *r_err, + Ref r_state) { + if (r_state == Ref()) { + r_state.instance(); + } + r_state->use_named_skin_binds = + p_flags & EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; + r_state->use_legacy_names = + p_flags & EditorSceneImporter::IMPORT_USE_LEGACY_NAMES; + + Ref gltf_document; + gltf_document.instance(); + Error err = gltf_document->parse(r_state, p_path); + *r_err = err; + ERR_FAIL_COND_V(err != Error::OK, nullptr); + + Spatial *root = memnew(Spatial); + if (r_state->use_legacy_names) { + root->set_name(gltf_document->_legacy_validate_node_name(r_state->scene_name)); + } else { + root->set_name(r_state->scene_name); + } + for (int32_t root_i = 0; root_i < r_state->root_nodes.size(); root_i++) { + gltf_document->_generate_scene_node(r_state, root, root, r_state->root_nodes[root_i]); + } + gltf_document->_process_mesh_instances(r_state, root); + if (r_state->animations.size()) { + AnimationPlayer *ap = memnew(AnimationPlayer); + root->add_child(ap); + ap->set_owner(root); + for (int i = 0; i < r_state->animations.size(); i++) { + gltf_document->_import_animation(r_state, ap, i, p_bake_fps); + } + } + + return cast_to(root); +} + +void PackedSceneGLTF::pack_gltf(String p_path, int32_t p_flags, + real_t p_bake_fps, Ref r_state) { + Error err = FAILED; + List deps; + Node *root = import_scene(p_path, p_flags, p_bake_fps, &deps, &err, r_state); + ERR_FAIL_COND(err != OK); + pack(root); +} + +void PackedSceneGLTF::save_scene(Node *p_node, const String &p_path, + const String &p_src_path, uint32_t p_flags, + int p_bake_fps, List *r_missing_deps, + Error *r_err) { + Error err = FAILED; + if (r_err) { + *r_err = err; + } + Ref gltf_document; + gltf_document.instance(); + Ref state; + state.instance(); + err = gltf_document->serialize(state, p_node, p_path); + if (r_err) { + *r_err = err; + } +} + +void PackedSceneGLTF::_build_parent_hierachy(Ref state) { + // build the hierarchy + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; + ERR_FAIL_INDEX(child_i, state->nodes.size()); + if (state->nodes.write[child_i]->parent != -1) { + continue; + } + state->nodes.write[child_i]->parent = node_i; + } + } +} + +Error PackedSceneGLTF::export_gltf(Node *p_root, String p_path, + int32_t p_flags, + real_t p_bake_fps) { + ERR_FAIL_COND_V(!p_root, FAILED); + List deps; + Error err; + String path = p_path; + int32_t flags = p_flags; + real_t baked_fps = p_bake_fps; + Ref exporter; + exporter.instance(); + exporter->save_scene(p_root, path, "", flags, baked_fps, &deps, &err); + int32_t error_code = err; + if (error_code != 0) { + return Error(error_code); + } + return OK; +} diff --git a/modules/gltf/editor_scene_importer_gltf.h b/modules/gltf/editor_scene_importer_gltf.h new file mode 100644 index 0000000000..7e69ba3def --- /dev/null +++ b/modules/gltf/editor_scene_importer_gltf.h @@ -0,0 +1,96 @@ +/*************************************************************************/ +/* editor_scene_importer_gltf.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 EDITOR_SCENE_IMPORTER_GLTF_H +#define EDITOR_SCENE_IMPORTER_GLTF_H + +#include "core/io/json.h" +#include "core/object.h" +#include "core/project_settings.h" +#include "core/vector.h" +#include "editor/import/resource_importer_scene.h" +#include "modules/csg/csg_shape.h" +#include "modules/gridmap/grid_map.h" +#include "scene/3d/mesh_instance.h" +#include "scene/3d/multimesh_instance.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/spatial.h" +#include "scene/animation/animation_player.h" +#include "scene/gui/check_box.h" +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" +#include "scene/resources/surface_tool.h" + +#include "gltf_document.h" +#include "gltf_state.h" + +class AnimationPlayer; +class BoneAttachment; +class EditorSceneImporterMeshNode3D; + +#ifdef TOOLS_ENABLED +class EditorSceneImporterGLTF : public EditorSceneImporter { + GDCLASS(EditorSceneImporterGLTF, EditorSceneImporter); + +public: + virtual uint32_t get_import_flags() const; + virtual void get_extensions(List *r_extensions) const; + virtual Node *import_scene(const String &p_path, uint32_t p_flags, + int p_bake_fps, + List *r_missing_deps = NULL, + Error *r_err = NULL); + virtual Ref import_animation(const String &p_path, + uint32_t p_flags, int p_bake_fps); +}; +#endif + +class PackedSceneGLTF : public PackedScene { + GDCLASS(PackedSceneGLTF, PackedScene); + +protected: + static void _bind_methods(); + +public: + virtual void save_scene(Node *p_node, const String &p_path, const String &p_src_path, + uint32_t p_flags, int p_bake_fps, + List *r_missing_deps, Error *r_err = NULL); + virtual void _build_parent_hierachy(Ref state); + virtual Error export_gltf(Node *p_root, String p_path, int32_t p_flags = 0, + real_t p_bake_fps = 1000.0f); + virtual Node *import_scene(const String &p_path, uint32_t p_flags, + int p_bake_fps, + List *r_missing_deps, + Error *r_err, + Ref r_state); + virtual Node *import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref r_state = Ref()); + virtual void pack_gltf(String p_path, int32_t p_flags = 0, + real_t p_bake_fps = 1000.0f, Ref r_state = Ref()); +}; +#endif // EDITOR_SCENE_IMPORTER_GLTF_H diff --git a/modules/gltf/gltf_accessor.cpp b/modules/gltf/gltf_accessor.cpp new file mode 100644 index 0000000000..bf6de76fa3 --- /dev/null +++ b/modules/gltf/gltf_accessor.cpp @@ -0,0 +1,189 @@ +/*************************************************************************/ +/* gltf_accessor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_accessor.h" + +void GLTFAccessor::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_buffer_view"), &GLTFAccessor::get_buffer_view); + ClassDB::bind_method(D_METHOD("set_buffer_view", "buffer_view"), &GLTFAccessor::set_buffer_view); + ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFAccessor::get_byte_offset); + ClassDB::bind_method(D_METHOD("set_byte_offset", "byte_offset"), &GLTFAccessor::set_byte_offset); + ClassDB::bind_method(D_METHOD("get_component_type"), &GLTFAccessor::get_component_type); + ClassDB::bind_method(D_METHOD("set_component_type", "component_type"), &GLTFAccessor::set_component_type); + ClassDB::bind_method(D_METHOD("get_normalized"), &GLTFAccessor::get_normalized); + ClassDB::bind_method(D_METHOD("set_normalized", "normalized"), &GLTFAccessor::set_normalized); + ClassDB::bind_method(D_METHOD("get_count"), &GLTFAccessor::get_count); + ClassDB::bind_method(D_METHOD("set_count", "count"), &GLTFAccessor::set_count); + ClassDB::bind_method(D_METHOD("get_type"), &GLTFAccessor::get_type); + ClassDB::bind_method(D_METHOD("set_type", "type"), &GLTFAccessor::set_type); + ClassDB::bind_method(D_METHOD("get_min"), &GLTFAccessor::get_min); + ClassDB::bind_method(D_METHOD("set_min", "min"), &GLTFAccessor::set_min); + ClassDB::bind_method(D_METHOD("get_max"), &GLTFAccessor::get_max); + ClassDB::bind_method(D_METHOD("set_max", "max"), &GLTFAccessor::set_max); + ClassDB::bind_method(D_METHOD("get_sparse_count"), &GLTFAccessor::get_sparse_count); + ClassDB::bind_method(D_METHOD("set_sparse_count", "sparse_count"), &GLTFAccessor::set_sparse_count); + ClassDB::bind_method(D_METHOD("get_sparse_indices_buffer_view"), &GLTFAccessor::get_sparse_indices_buffer_view); + ClassDB::bind_method(D_METHOD("set_sparse_indices_buffer_view", "sparse_indices_buffer_view"), &GLTFAccessor::set_sparse_indices_buffer_view); + ClassDB::bind_method(D_METHOD("get_sparse_indices_byte_offset"), &GLTFAccessor::get_sparse_indices_byte_offset); + ClassDB::bind_method(D_METHOD("set_sparse_indices_byte_offset", "sparse_indices_byte_offset"), &GLTFAccessor::set_sparse_indices_byte_offset); + ClassDB::bind_method(D_METHOD("get_sparse_indices_component_type"), &GLTFAccessor::get_sparse_indices_component_type); + ClassDB::bind_method(D_METHOD("set_sparse_indices_component_type", "sparse_indices_component_type"), &GLTFAccessor::set_sparse_indices_component_type); + ClassDB::bind_method(D_METHOD("get_sparse_values_buffer_view"), &GLTFAccessor::get_sparse_values_buffer_view); + ClassDB::bind_method(D_METHOD("set_sparse_values_buffer_view", "sparse_values_buffer_view"), &GLTFAccessor::set_sparse_values_buffer_view); + ClassDB::bind_method(D_METHOD("get_sparse_values_byte_offset"), &GLTFAccessor::get_sparse_values_byte_offset); + ClassDB::bind_method(D_METHOD("set_sparse_values_byte_offset", "sparse_values_byte_offset"), &GLTFAccessor::set_sparse_values_byte_offset); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "buffer_view"), "set_buffer_view", "get_buffer_view"); // GLTFBufferViewIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "byte_offset"), "set_byte_offset", "get_byte_offset"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "component_type"), "set_component_type", "get_component_type"); // int + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalized"), "set_normalized", "get_normalized"); // bool + ADD_PROPERTY(PropertyInfo(Variant::INT, "count"), "set_count", "get_count"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_type", "get_type"); // GLTFDocument::GLTFType + ADD_PROPERTY(PropertyInfo(Variant::POOL_REAL_ARRAY, "min"), "set_min", "get_min"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::POOL_REAL_ARRAY, "max"), "set_max", "get_max"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_count"), "set_sparse_count", "get_sparse_count"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_indices_buffer_view"), "set_sparse_indices_buffer_view", "get_sparse_indices_buffer_view"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_indices_byte_offset"), "set_sparse_indices_byte_offset", "get_sparse_indices_byte_offset"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_indices_component_type"), "set_sparse_indices_component_type", "get_sparse_indices_component_type"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_values_buffer_view"), "set_sparse_values_buffer_view", "get_sparse_values_buffer_view"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_values_byte_offset"), "set_sparse_values_byte_offset", "get_sparse_values_byte_offset"); // int +} + +GLTFBufferViewIndex GLTFAccessor::get_buffer_view() { + return buffer_view; +} + +void GLTFAccessor::set_buffer_view(GLTFBufferViewIndex p_buffer_view) { + buffer_view = p_buffer_view; +} + +int GLTFAccessor::get_byte_offset() { + return byte_offset; +} + +void GLTFAccessor::set_byte_offset(int p_byte_offset) { + byte_offset = p_byte_offset; +} + +int GLTFAccessor::get_component_type() { + return component_type; +} + +void GLTFAccessor::set_component_type(int p_component_type) { + component_type = p_component_type; +} + +bool GLTFAccessor::get_normalized() { + return normalized; +} + +void GLTFAccessor::set_normalized(bool p_normalized) { + normalized = p_normalized; +} + +int GLTFAccessor::get_count() { + return count; +} + +void GLTFAccessor::set_count(int p_count) { + count = p_count; +} + +int GLTFAccessor::get_type() { + return (int)type; +} + +void GLTFAccessor::set_type(int p_type) { + type = (GLTFDocument::GLTFType)p_type; // TODO: Register enum +} + +PoolVector GLTFAccessor::get_min() { + return min; +} + +void GLTFAccessor::set_min(PoolVector p_min) { + min = p_min; +} + +PoolVector GLTFAccessor::get_max() { + return max; +} + +void GLTFAccessor::set_max(PoolVector p_max) { + max = p_max; +} + +int GLTFAccessor::get_sparse_count() { + return sparse_count; +} + +void GLTFAccessor::set_sparse_count(int p_sparse_count) { + sparse_count = p_sparse_count; +} + +int GLTFAccessor::get_sparse_indices_buffer_view() { + return sparse_indices_buffer_view; +} + +void GLTFAccessor::set_sparse_indices_buffer_view(int p_sparse_indices_buffer_view) { + sparse_indices_buffer_view = p_sparse_indices_buffer_view; +} + +int GLTFAccessor::get_sparse_indices_byte_offset() { + return sparse_indices_byte_offset; +} + +void GLTFAccessor::set_sparse_indices_byte_offset(int p_sparse_indices_byte_offset) { + sparse_indices_byte_offset = p_sparse_indices_byte_offset; +} + +int GLTFAccessor::get_sparse_indices_component_type() { + return sparse_indices_component_type; +} + +void GLTFAccessor::set_sparse_indices_component_type(int p_sparse_indices_component_type) { + sparse_indices_component_type = p_sparse_indices_component_type; +} + +int GLTFAccessor::get_sparse_values_buffer_view() { + return sparse_values_buffer_view; +} + +void GLTFAccessor::set_sparse_values_buffer_view(int p_sparse_values_buffer_view) { + sparse_values_buffer_view = p_sparse_values_buffer_view; +} + +int GLTFAccessor::get_sparse_values_byte_offset() { + return sparse_values_byte_offset; +} + +void GLTFAccessor::set_sparse_values_byte_offset(int p_sparse_values_byte_offset) { + sparse_values_byte_offset = p_sparse_values_byte_offset; +} diff --git a/modules/gltf/gltf_accessor.h b/modules/gltf/gltf_accessor.h new file mode 100644 index 0000000000..9b42d4d735 --- /dev/null +++ b/modules/gltf/gltf_accessor.h @@ -0,0 +1,104 @@ +/*************************************************************************/ +/* gltf_accessor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_ACCESSOR_H +#define GLTF_ACCESSOR_H + +#include "core/resource.h" +#include "gltf_document.h" + +struct GLTFAccessor : public Resource { + GDCLASS(GLTFAccessor, Resource); + friend class GLTFDocument; + +private: + GLTFBufferViewIndex buffer_view = 0; + int byte_offset = 0; + int component_type = 0; + bool normalized = false; + int count = 0; + GLTFDocument::GLTFType + type = GLTFDocument::TYPE_SCALAR; + PoolVector min; + PoolVector max; + int sparse_count = 0; + int sparse_indices_buffer_view = 0; + int sparse_indices_byte_offset = 0; + int sparse_indices_component_type = 0; + int sparse_values_buffer_view = 0; + int sparse_values_byte_offset = 0; + +protected: + static void _bind_methods(); + +public: + GLTFBufferViewIndex get_buffer_view(); + void set_buffer_view(GLTFBufferViewIndex p_buffer_view); + + int get_byte_offset(); + void set_byte_offset(int p_byte_offset); + + int get_component_type(); + void set_component_type(int p_component_type); + + bool get_normalized(); + void set_normalized(bool p_normalized); + + int get_count(); + void set_count(int p_count); + + int get_type(); + void set_type(int p_type); + + PoolVector get_min(); + void set_min(PoolVector p_min); + + PoolVector get_max(); + void set_max(PoolVector p_max); + + int get_sparse_count(); + void set_sparse_count(int p_sparse_count); + + int get_sparse_indices_buffer_view(); + void set_sparse_indices_buffer_view(int p_sparse_indices_buffer_view); + + int get_sparse_indices_byte_offset(); + void set_sparse_indices_byte_offset(int p_sparse_indices_byte_offset); + + int get_sparse_indices_component_type(); + void set_sparse_indices_component_type(int p_sparse_indices_component_type); + + int get_sparse_values_buffer_view(); + void set_sparse_values_buffer_view(int p_sparse_values_buffer_view); + + int get_sparse_values_byte_offset(); + void set_sparse_values_byte_offset(int p_sparse_values_byte_offset); +}; +#endif // GLTF_ACCESSOR_H diff --git a/modules/gltf/gltf_animation.cpp b/modules/gltf/gltf_animation.cpp new file mode 100644 index 0000000000..889a8e8870 --- /dev/null +++ b/modules/gltf/gltf_animation.cpp @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* gltf_animation.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_animation.h" + +void GLTFAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_loop"), &GLTFAnimation::get_loop); + ClassDB::bind_method(D_METHOD("set_loop", "loop"), &GLTFAnimation::set_loop); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "get_loop"); // bool +} + +bool GLTFAnimation::get_loop() const { + return loop; +} + +void GLTFAnimation::set_loop(bool p_val) { + loop = p_val; +} + +Map &GLTFAnimation::get_tracks() { + return tracks; +} + +GLTFAnimation::GLTFAnimation() { +} diff --git a/modules/gltf/gltf_animation.h b/modules/gltf/gltf_animation.h new file mode 100644 index 0000000000..258cbbd420 --- /dev/null +++ b/modules/gltf/gltf_animation.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* gltf_animation.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_ANIMATION_H +#define GLTF_ANIMATION_H + +#include "core/resource.h" + +class GLTFAnimation : public Resource { + GDCLASS(GLTFAnimation, Resource); + +protected: + static void _bind_methods(); + +public: + enum Interpolation { + INTERP_LINEAR, + INTERP_STEP, + INTERP_CATMULLROMSPLINE, + INTERP_CUBIC_SPLINE, + }; + + template + struct Channel { + Interpolation interpolation; + Vector times; + Vector values; + }; + + struct Track { + Channel translation_track; + Channel rotation_track; + Channel scale_track; + Vector> weight_tracks; + }; + +public: + bool get_loop() const; + void set_loop(bool p_val); + Map &get_tracks(); + GLTFAnimation(); + +private: + bool loop = false; + Map tracks; +}; +#endif // GLTF_ANIMATION_H diff --git a/modules/gltf/gltf_buffer_view.cpp b/modules/gltf/gltf_buffer_view.cpp new file mode 100644 index 0000000000..ba38a11c4c --- /dev/null +++ b/modules/gltf/gltf_buffer_view.cpp @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* gltf_buffer_view.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_buffer_view.h" + +void GLTFBufferView::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_buffer"), &GLTFBufferView::get_buffer); + ClassDB::bind_method(D_METHOD("set_buffer", "buffer"), &GLTFBufferView::set_buffer); + ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFBufferView::get_byte_offset); + ClassDB::bind_method(D_METHOD("set_byte_offset", "byte_offset"), &GLTFBufferView::set_byte_offset); + ClassDB::bind_method(D_METHOD("get_byte_length"), &GLTFBufferView::get_byte_length); + ClassDB::bind_method(D_METHOD("set_byte_length", "byte_length"), &GLTFBufferView::set_byte_length); + ClassDB::bind_method(D_METHOD("get_byte_stride"), &GLTFBufferView::get_byte_stride); + ClassDB::bind_method(D_METHOD("set_byte_stride", "byte_stride"), &GLTFBufferView::set_byte_stride); + ClassDB::bind_method(D_METHOD("get_indices"), &GLTFBufferView::get_indices); + ClassDB::bind_method(D_METHOD("set_indices", "indices"), &GLTFBufferView::set_indices); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "buffer"), "set_buffer", "get_buffer"); // GLTFBufferIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "byte_offset"), "set_byte_offset", "get_byte_offset"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "byte_length"), "set_byte_length", "get_byte_length"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "byte_stride"), "set_byte_stride", "get_byte_stride"); // int + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indices"), "set_indices", "get_indices"); // bool +} + +GLTFBufferIndex GLTFBufferView::get_buffer() { + return buffer; +} + +void GLTFBufferView::set_buffer(GLTFBufferIndex p_buffer) { + buffer = p_buffer; +} + +int GLTFBufferView::get_byte_offset() { + return byte_offset; +} + +void GLTFBufferView::set_byte_offset(int p_byte_offset) { + byte_offset = p_byte_offset; +} + +int GLTFBufferView::get_byte_length() { + return byte_length; +} + +void GLTFBufferView::set_byte_length(int p_byte_length) { + byte_length = p_byte_length; +} + +int GLTFBufferView::get_byte_stride() { + return byte_stride; +} + +void GLTFBufferView::set_byte_stride(int p_byte_stride) { + byte_stride = p_byte_stride; +} + +bool GLTFBufferView::get_indices() { + return indices; +} + +void GLTFBufferView::set_indices(bool p_indices) { + indices = p_indices; +} diff --git a/modules/gltf/gltf_buffer_view.h b/modules/gltf/gltf_buffer_view.h new file mode 100644 index 0000000000..999017ccf9 --- /dev/null +++ b/modules/gltf/gltf_buffer_view.h @@ -0,0 +1,68 @@ +/*************************************************************************/ +/* gltf_buffer_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_BUFFER_VIEW_H +#define GLTF_BUFFER_VIEW_H + +#include "core/resource.h" +#include "gltf_document.h" + +class GLTFBufferView : public Resource { + GDCLASS(GLTFBufferView, Resource); + friend class GLTFDocument; + +private: + GLTFBufferIndex buffer = -1; + int byte_offset = 0; + int byte_length = 0; + int byte_stride = -1; + bool indices = false; + +protected: + static void _bind_methods(); + +public: + GLTFBufferIndex get_buffer(); + void set_buffer(GLTFBufferIndex p_buffer); + + int get_byte_offset(); + void set_byte_offset(int p_byte_offset); + + int get_byte_length(); + void set_byte_length(int p_byte_length); + + int get_byte_stride(); + void set_byte_stride(int p_byte_stride); + + bool get_indices(); + void set_indices(bool p_indices); + // matrices need to be transformed to this +}; +#endif // GLTF_BUFFER_VIEW_H diff --git a/modules/gltf/gltf_camera.cpp b/modules/gltf/gltf_camera.cpp new file mode 100644 index 0000000000..13e613e137 --- /dev/null +++ b/modules/gltf/gltf_camera.cpp @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* gltf_camera.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_camera.h" + +void GLTFCamera::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_perspective"), &GLTFCamera::get_perspective); + ClassDB::bind_method(D_METHOD("set_perspective", "perspective"), &GLTFCamera::set_perspective); + ClassDB::bind_method(D_METHOD("get_fov_size"), &GLTFCamera::get_fov_size); + ClassDB::bind_method(D_METHOD("set_fov_size", "fov_size"), &GLTFCamera::set_fov_size); + ClassDB::bind_method(D_METHOD("get_zfar"), &GLTFCamera::get_zfar); + ClassDB::bind_method(D_METHOD("set_zfar", "zfar"), &GLTFCamera::set_zfar); + ClassDB::bind_method(D_METHOD("get_znear"), &GLTFCamera::get_znear); + ClassDB::bind_method(D_METHOD("set_znear", "znear"), &GLTFCamera::set_znear); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "perspective"), "set_perspective", "get_perspective"); // bool + ADD_PROPERTY(PropertyInfo(Variant::REAL, "fov_size"), "set_fov_size", "get_fov_size"); // float + ADD_PROPERTY(PropertyInfo(Variant::REAL, "zfar"), "set_zfar", "get_zfar"); // float + ADD_PROPERTY(PropertyInfo(Variant::REAL, "znear"), "set_znear", "get_znear"); // float +} diff --git a/modules/gltf/gltf_camera.h b/modules/gltf/gltf_camera.h new file mode 100644 index 0000000000..47ab4885b9 --- /dev/null +++ b/modules/gltf/gltf_camera.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* gltf_camera.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_CAMERA_H +#define GLTF_CAMERA_H + +#include "core/resource.h" + +class GLTFCamera : public Resource { + GDCLASS(GLTFCamera, Resource); + +private: + bool perspective = true; + float fov_size = 75.0; + float zfar = 4000.0; + float znear = 0.05; + +protected: + static void _bind_methods(); + +public: + bool get_perspective() const { return perspective; } + void set_perspective(bool p_val) { perspective = p_val; } + float get_fov_size() const { return fov_size; } + void set_fov_size(float p_val) { fov_size = p_val; } + float get_zfar() const { return zfar; } + void set_zfar(float p_val) { zfar = p_val; } + float get_znear() const { return znear; } + void set_znear(float p_val) { znear = p_val; } +}; +#endif // GLTF_CAMERA_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp new file mode 100644 index 0000000000..f8e30a889c --- /dev/null +++ b/modules/gltf/gltf_document.cpp @@ -0,0 +1,6737 @@ +/*************************************************************************/ +/* gltf_document.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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.h" +#include "core/error_list.h" +#include "core/error_macros.h" +#include "core/variant.h" +#include "gltf_accessor.h" +#include "gltf_animation.h" +#include "gltf_camera.h" +#include "gltf_light.h" +#include "gltf_mesh.h" +#include "gltf_node.h" +#include "gltf_skeleton.h" +#include "gltf_skin.h" +#include "gltf_spec_gloss.h" +#include "gltf_state.h" +#include "gltf_texture.h" + +#include +#include + +#include "core/bind/core_bind.h" +#include "core/crypto/crypto_core.h" +#include "core/io/json.h" +#include "core/math/disjoint_set.h" +#include "core/os/file_access.h" +#include "core/variant.h" +#include "core/version.h" +#include "core/version_hash.gen.h" +#include "drivers/png/png_driver_common.h" +#include "editor/import/resource_importer_scene.h" +#ifdef MODULE_CSG_ENABLED +#include "modules/csg/csg_shape.h" +#endif // MODULE_CSG_ENABLED +#ifdef MODULE_GRIDMAP_ENABLED +#include "modules/gridmap/grid_map.h" +#endif // MODULE_GRIDMAP_ENABLED +#include "modules/regex/regex.h" +#include "scene/2d/node_2d.h" +#include "scene/3d/bone_attachment.h" +#include "scene/3d/camera.h" +#include "scene/3d/mesh_instance.h" +#include "scene/3d/multimesh_instance.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/spatial.h" +#include "scene/animation/animation_player.h" +#include "scene/main/node.h" +#include "scene/resources/surface_tool.h" +#include + +Error GLTFDocument::serialize(Ref state, Node *p_root, const String &p_path) { + uint64_t begin_time = OS::get_singleton()->get_ticks_usec(); + + _convert_scene_node(state, p_root, p_root, -1, -1); + if (!state->buffers.size()) { + state->buffers.push_back(Vector()); + } + + /* STEP 1 CONVERT MESH INSTANCES */ + _convert_mesh_instances(state); + + /* STEP 2 SERIALIZE CAMERAS */ + Error err = _serialize_cameras(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 3 CREATE SKINS */ + err = _serialize_skins(state); + if (err != OK) { + return Error::FAILED; + } + /* STEP 4 CREATE BONE ATTACHMENTS */ + err = _serialize_bone_attachment(state); + if (err != OK) { + return Error::FAILED; + } + /* STEP 5 SERIALIZE MESHES (we have enough info now) */ + err = _serialize_meshes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 6 SERIALIZE TEXTURES */ + err = _serialize_materials(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 7 SERIALIZE IMAGES */ + err = _serialize_images(state, p_path); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 8 SERIALIZE TEXTURES */ + err = _serialize_textures(state); + if (err != OK) { + return Error::FAILED; + } + + // /* STEP 9 SERIALIZE ANIMATIONS */ + err = _serialize_animations(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 10 SERIALIZE ACCESSORS */ + err = _encode_accessors(state); + if (err != OK) { + return Error::FAILED; + } + + for (GLTFBufferViewIndex i = 0; i < state->buffer_views.size(); i++) { + state->buffer_views.write[i]->buffer = 0; + } + + /* STEP 11 SERIALIZE BUFFER VIEWS */ + err = _encode_buffer_views(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 12 SERIALIZE NODES */ + err = _serialize_nodes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 13 SERIALIZE SCENE */ + err = _serialize_scenes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 14 SERIALIZE SCENE */ + err = _serialize_lights(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 15 SERIALIZE EXTENSIONS */ + err = _serialize_extensions(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 16 SERIALIZE VERSION */ + err = _serialize_version(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 17 SERIALIZE FILE */ + err = _serialize_file(state, p_path); + if (err != OK) { + return Error::FAILED; + } + 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); + print_line("glTF: Export time elapsed seconds " + rtos(elapsed_sec).pad_decimals(2)); + + return OK; +} + +Error GLTFDocument::_serialize_extensions(Ref state) const { + const String texture_transform = "KHR_texture_transform"; + const String punctual_lights = "KHR_lights_punctual"; + Array extensions_used; + extensions_used.push_back(punctual_lights); + extensions_used.push_back(texture_transform); + state->json["extensionsUsed"] = extensions_used; + Array extensions_required; + extensions_required.push_back(texture_transform); + state->json["extensionsRequired"] = extensions_required; + return OK; +} + +Error GLTFDocument::_serialize_scenes(Ref state) { + Array scenes; + const int loaded_scene = 0; + state->json["scene"] = loaded_scene; + + if (state->nodes.size()) { + Dictionary s; + if (!state->scene_name.empty()) { + s["name"] = state->scene_name; + } + + Array nodes; + nodes.push_back(0); + s["nodes"] = nodes; + scenes.push_back(s); + } + state->json["scenes"] = scenes; + + return OK; +} + +Error GLTFDocument::_parse_json(const String &p_path, Ref state) { + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (!f) { + return err; + } + + Vector array; + array.resize(f->get_len()); + f->get_buffer(array.ptrw(), array.size()); + String text; + text.parse_utf8((const char *)array.ptr(), array.size()); + + String err_txt; + int err_line; + Variant v; + err = JSON::parse(text, v, err_txt, err_line); + if (err != OK) { + _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); + return err; + } + state->json = v; + + return OK; +} + +Error GLTFDocument::_serialize_bone_attachment(Ref state) { + for (int skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) { + for (int attachment_i = 0; attachment_i < state->skeletons[skeleton_i]->bone_attachments.size(); attachment_i++) { + BoneAttachment *bone_attachment = state->skeletons[skeleton_i]->bone_attachments[attachment_i]; + String bone_name = bone_attachment->get_bone_name(); + bone_name = _sanitize_bone_name(state, bone_name); + int32_t bone = state->skeletons[skeleton_i]->godot_skeleton->find_bone(bone_name); + ERR_CONTINUE(bone == -1); + for (int skin_i = 0; skin_i < state->skins.size(); skin_i++) { + if (state->skins[skin_i]->skeleton != skeleton_i) { + continue; + } + + for (int node_i = 0; node_i < bone_attachment->get_child_count(); node_i++) { + ERR_CONTINUE(bone >= state->skins[skin_i]->joints.size()); + _convert_scene_node(state, bone_attachment->get_child(node_i), bone_attachment->get_owner(), state->skins[skin_i]->joints[bone], 0); + } + break; + } + } + } + return OK; +} + +Error GLTFDocument::_parse_glb(const String &p_path, Ref state) { + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (!f) { + return err; + } + + uint32_t magic = f->get_32(); + ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF + f->get_32(); // version + f->get_32(); // length + + uint32_t chunk_length = f->get_32(); + uint32_t chunk_type = f->get_32(); + + ERR_FAIL_COND_V(chunk_type != 0x4E4F534A, ERR_PARSE_ERROR); //JSON + Vector json_data; + json_data.resize(chunk_length); + uint32_t len = f->get_buffer(json_data.ptrw(), chunk_length); + ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); + + String text; + text.parse_utf8((const char *)json_data.ptr(), json_data.size()); + + String err_txt; + int err_line; + Variant v; + err = JSON::parse(text, v, err_txt, err_line); + if (err != OK) { + _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); + return err; + } + + state->json = v; + + //data? + + chunk_length = f->get_32(); + chunk_type = f->get_32(); + + if (f->eof_reached()) { + return OK; //all good + } + + ERR_FAIL_COND_V(chunk_type != 0x004E4942, ERR_PARSE_ERROR); //BIN + + state->glb_data.resize(chunk_length); + len = f->get_buffer(state->glb_data.ptrw(), chunk_length); + ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); + + return OK; +} + +static Array _vec3_to_arr(const Vector3 &p_vec3) { + Array array; + array.resize(3); + array[0] = p_vec3.x; + array[1] = p_vec3.y; + array[2] = p_vec3.z; + return array; +} + +static Vector3 _arr_to_vec3(const Array &p_array) { + ERR_FAIL_COND_V(p_array.size() != 3, Vector3()); + return Vector3(p_array[0], p_array[1], p_array[2]); +} + +static Array _quat_to_array(const Quat &p_quat) { + Array array; + array.resize(4); + array[0] = p_quat.x; + array[1] = p_quat.y; + array[2] = p_quat.z; + array[3] = p_quat.w; + return array; +} + +static Quat _arr_to_quat(const Array &p_array) { + ERR_FAIL_COND_V(p_array.size() != 4, Quat()); + return Quat(p_array[0], p_array[1], p_array[2], p_array[3]); +} + +static Transform _arr_to_xform(const Array &p_array) { + ERR_FAIL_COND_V(p_array.size() != 16, Transform()); + + Transform xform; + xform.basis.set_axis(Vector3::AXIS_X, Vector3(p_array[0], p_array[1], p_array[2])); + xform.basis.set_axis(Vector3::AXIS_Y, Vector3(p_array[4], p_array[5], p_array[6])); + xform.basis.set_axis(Vector3::AXIS_Z, Vector3(p_array[8], p_array[9], p_array[10])); + xform.set_origin(Vector3(p_array[12], p_array[13], p_array[14])); + + return xform; +} + +static Vector _xform_to_array(const Transform p_transform) { + Vector array; + array.resize(16); + Vector3 axis_x = p_transform.get_basis().get_axis(Vector3::AXIS_X); + array.write[0] = axis_x.x; + array.write[1] = axis_x.y; + array.write[2] = axis_x.z; + array.write[3] = 0.0f; + Vector3 axis_y = p_transform.get_basis().get_axis(Vector3::AXIS_Y); + array.write[4] = axis_y.x; + array.write[5] = axis_y.y; + array.write[6] = axis_y.z; + array.write[7] = 0.0f; + Vector3 axis_z = p_transform.get_basis().get_axis(Vector3::AXIS_Z); + array.write[8] = axis_z.x; + array.write[9] = axis_z.y; + array.write[10] = axis_z.z; + array.write[11] = 0.0f; + Vector3 origin = p_transform.get_origin(); + array.write[12] = origin.x; + array.write[13] = origin.y; + array.write[14] = origin.z; + array.write[15] = 1.0f; + return array; +} + +Error GLTFDocument::_serialize_nodes(Ref state) { + Array nodes; + for (int i = 0; i < state->nodes.size(); i++) { + Dictionary node; + Ref n = state->nodes[i]; + Dictionary extensions; + node["extensions"] = extensions; + if (!n->get_name().empty()) { + node["name"] = n->get_name(); + } + if (n->camera != -1) { + node["camera"] = n->camera; + } + if (n->light != -1) { + Dictionary lights_punctual; + extensions["KHR_lights_punctual"] = lights_punctual; + lights_punctual["light"] = n->light; + } + if (n->mesh != -1) { + node["mesh"] = n->mesh; + } + if (n->skin != -1) { + node["skin"] = n->skin; + } + if (n->skeleton != -1 && n->skin < 0) { + } + if (n->xform != Transform()) { + node["matrix"] = _xform_to_array(n->xform); + } + + if (!n->rotation.is_equal_approx(Quat())) { + node["rotation"] = _quat_to_array(n->rotation); + } + + if (!n->scale.is_equal_approx(Vector3(1.0f, 1.0f, 1.0f))) { + node["scale"] = _vec3_to_arr(n->scale); + } + + 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++) { + children.push_back(n->children[j]); + } + node["children"] = children; + } + nodes.push_back(node); + } + state->json["nodes"] = nodes; + return OK; +} + +String GLTFDocument::_sanitize_scene_name(Ref state, const String &p_name) { + if (state->use_legacy_names) { + RegEx regex("([^a-zA-Z0-9_ -]+)"); + String s_name = regex.sub(p_name, "", true); + return s_name; + } else { + return p_name.validate_node_name(); + } +} + +String GLTFDocument::_legacy_validate_node_name(const String &p_name) { + String invalid_character = ". : @ / \""; + String name = p_name; + Vector chars = invalid_character.split(" "); + for (int i = 0; i < chars.size(); i++) { + name = name.replace(chars[i], ""); + } + return name; +} + +String GLTFDocument::_gen_unique_name(Ref state, const String &p_name) { + const String s_name = _sanitize_scene_name(state, p_name); + + String name; + int index = 1; + while (true) { + name = s_name; + + if (index > 1) { + if (state->use_legacy_names) { + name += " "; + } + name += itos(index); + } + if (!state->unique_names.has(name)) { + break; + } + index++; + } + + state->unique_names.insert(name); + + return name; +} + +String GLTFDocument::_sanitize_animation_name(const String &p_name) { + // Animations disallow the normal node invalid characters as well as "," and "[" + // (See animation/animation_player.cpp::add_animation) + + // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node. + String name = p_name.validate_node_name(); + name = name.replace(",", ""); + name = name.replace("[", ""); + return name; +} + +String GLTFDocument::_gen_unique_animation_name(Ref state, const String &p_name) { + const String s_name = _sanitize_animation_name(p_name); + + String name; + int index = 1; + while (true) { + name = s_name; + + if (index > 1) { + name += itos(index); + } + if (!state->unique_animation_names.has(name)) { + break; + } + index++; + } + + state->unique_animation_names.insert(name); + + return name; +} + +String GLTFDocument::_sanitize_bone_name(Ref state, const String &p_name) { + if (state->use_legacy_names) { + String name = p_name.camelcase_to_underscore(true); + RegEx pattern_del("([^a-zA-Z0-9_ ])+"); + + name = pattern_del.sub(name, "", true); + + RegEx pattern_nospace(" +"); + name = pattern_nospace.sub(name, "_", true); + + RegEx pattern_multiple("_+"); + name = pattern_multiple.sub(name, "_", true); + + RegEx pattern_padded("0+(\\d+)"); + name = pattern_padded.sub(name, "$1", true); + + return name; + } else { + String name = p_name; + name = name.replace(":", "_"); + name = name.replace("/", "_"); + if (name.empty()) { + name = "bone"; + } + return name; + } +} + +String GLTFDocument::_gen_unique_bone_name(Ref state, const GLTFSkeletonIndex skel_i, const String &p_name) { + String s_name = _sanitize_bone_name(state, p_name); + String name; + int index = 1; + while (true) { + name = s_name; + + if (index > 1) { + name += "_" + itos(index); + } + if (!state->skeletons[skel_i]->unique_names.has(name)) { + break; + } + index++; + } + + state->skeletons.write[skel_i]->unique_names.insert(name); + + return name; +} + +Error GLTFDocument::_parse_scenes(Ref state) { + ERR_FAIL_COND_V(!state->json.has("scenes"), ERR_FILE_CORRUPT); + const Array &scenes = state->json["scenes"]; + int loaded_scene = 0; + if (state->json.has("scene")) { + loaded_scene = state->json["scene"]; + } else { + WARN_PRINT("The load-time scene is not defined in the glTF2 file. Picking the first scene."); + } + + if (scenes.size()) { + ERR_FAIL_COND_V(loaded_scene >= scenes.size(), ERR_FILE_CORRUPT); + const Dictionary &s = scenes[loaded_scene]; + ERR_FAIL_COND_V(!s.has("nodes"), ERR_UNAVAILABLE); + const Array &nodes = s["nodes"]; + for (int j = 0; j < nodes.size(); j++) { + state->root_nodes.push_back(nodes[j]); + } + + if (s.has("name") && !String(s["name"]).empty() && !((String)s["name"]).begins_with("Scene")) { + state->scene_name = _gen_unique_name(state, s["name"]); + } else { + state->scene_name = _gen_unique_name(state, state->filename); + } + } + + return OK; +} + +Error GLTFDocument::_parse_nodes(Ref state) { + ERR_FAIL_COND_V(!state->json.has("nodes"), ERR_FILE_CORRUPT); + const Array &nodes = state->json["nodes"]; + for (int i = 0; i < nodes.size(); i++) { + Ref node; + node.instance(); + const Dictionary &n = nodes[i]; + + if (n.has("name")) { + node->set_name(n["name"]); + } + if (n.has("camera")) { + node->camera = n["camera"]; + } + if (n.has("mesh")) { + node->mesh = n["mesh"]; + } + if (n.has("skin")) { + node->skin = n["skin"]; + } + if (n.has("matrix")) { + node->xform = _arr_to_xform(n["matrix"]); + } else { + if (n.has("translation")) { + node->translation = _arr_to_vec3(n["translation"]); + } + if (n.has("rotation")) { + node->rotation = _arr_to_quat(n["rotation"]); + } + if (n.has("scale")) { + node->scale = _arr_to_vec3(n["scale"]); + } + + node->xform.basis.set_quat_scale(node->rotation, node->scale); + node->xform.origin = node->translation; + } + + if (n.has("extensions")) { + Dictionary extensions = n["extensions"]; + if (extensions.has("KHR_lights_punctual")) { + Dictionary lights_punctual = extensions["KHR_lights_punctual"]; + if (lights_punctual.has("light")) { + GLTFLightIndex light = lights_punctual["light"]; + node->light = light; + } + } + } + + if (n.has("children")) { + const Array &children = n["children"]; + for (int j = 0; j < children.size(); j++) { + node->children.push_back(children[j]); + } + } + + state->nodes.push_back(node); + } + + // build the hierarchy + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; + + ERR_FAIL_INDEX_V(child_i, state->nodes.size(), ERR_FILE_CORRUPT); + ERR_CONTINUE(state->nodes[child_i]->parent != -1); //node already has a parent, wtf. + + state->nodes.write[child_i]->parent = node_i; + } + } + + _compute_node_heights(state); + + return OK; +} + +void GLTFDocument::_compute_node_heights(Ref state) { + state->root_nodes.clear(); + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); ++node_i) { + Ref node = state->nodes[node_i]; + node->height = 0; + + GLTFNodeIndex current_i = node_i; + while (current_i >= 0) { + const GLTFNodeIndex parent_i = state->nodes[current_i]->parent; + if (parent_i >= 0) { + ++node->height; + } + current_i = parent_i; + } + + if (node->height == 0) { + state->root_nodes.push_back(node_i); + } + } +} + +static Vector _parse_base64_uri(const String &uri) { + int start = uri.find(","); + ERR_FAIL_COND_V(start == -1, Vector()); + + CharString substr = uri.right(start + 1).ascii(); + + int strlen = substr.length(); + + Vector buf; + buf.resize(strlen / 4 * 3 + 1 + 1); + + size_t len = 0; + ERR_FAIL_COND_V(CryptoCore::b64_decode(buf.ptrw(), buf.size(), &len, (unsigned char *)substr.get_data(), strlen) != OK, Vector()); + + buf.resize(len); + + return buf; +} +Error GLTFDocument::_encode_buffer_glb(Ref state, const String &p_path) { + print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); + + if (!state->buffers.size()) { + return OK; + } + Array buffers; + if (state->buffers.size()) { + Vector buffer_data = state->buffers[0]; + Dictionary gltf_buffer; + + gltf_buffer["byteLength"] = buffer_data.size(); + buffers.push_back(gltf_buffer); + } + + for (GLTFBufferIndex i = 1; i < state->buffers.size() - 1; i++) { + Vector buffer_data = state->buffers[i]; + Dictionary gltf_buffer; + String filename = p_path.get_basename().get_file() + itos(i) + ".bin"; + String path = p_path.get_base_dir() + "/" + filename; + Error err; + FileAccessRef f = FileAccess::open(path, FileAccess::WRITE, &err); + if (!f) { + return err; + } + if (buffer_data.size() == 0) { + return OK; + } + f->create(FileAccess::ACCESS_RESOURCES); + f->store_buffer(buffer_data.ptr(), buffer_data.size()); + f->close(); + gltf_buffer["uri"] = filename; + gltf_buffer["byteLength"] = buffer_data.size(); + buffers.push_back(gltf_buffer); + } + state->json["buffers"] = buffers; + + return OK; +} + +Error GLTFDocument::_encode_buffer_bins(Ref state, const String &p_path) { + print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); + + if (!state->buffers.size()) { + return OK; + } + Array buffers; + + for (GLTFBufferIndex i = 0; i < state->buffers.size(); i++) { + Vector buffer_data = state->buffers[i]; + Dictionary gltf_buffer; + String filename = p_path.get_basename().get_file() + itos(i) + ".bin"; + String path = p_path.get_base_dir() + "/" + filename; + Error err; + FileAccessRef f = FileAccess::open(path, FileAccess::WRITE, &err); + if (!f) { + return err; + } + if (buffer_data.size() == 0) { + return OK; + } + f->create(FileAccess::ACCESS_RESOURCES); + f->store_buffer(buffer_data.ptr(), buffer_data.size()); + f->close(); + gltf_buffer["uri"] = filename; + gltf_buffer["byteLength"] = buffer_data.size(); + buffers.push_back(gltf_buffer); + } + state->json["buffers"] = buffers; + + return OK; +} + +Error GLTFDocument::_parse_buffers(Ref state, const String &p_base_path) { + if (!state->json.has("buffers")) { + return OK; + } + + const Array &buffers = state->json["buffers"]; + for (GLTFBufferIndex i = 0; i < buffers.size(); i++) { + if (i == 0 && state->glb_data.size()) { + state->buffers.push_back(state->glb_data); + + } else { + const Dictionary &buffer = buffers[i]; + if (buffer.has("uri")) { + Vector buffer_data; + String uri = buffer["uri"]; + + if (uri.begins_with("data:")) { // Embedded data using base64. + // Validate data MIME types and throw an error if it's one we don't know/support. + if (!uri.begins_with("data:application/octet-stream;base64") && + !uri.begins_with("data:application/gltf-buffer;base64")) { + ERR_PRINT("glTF: Got buffer with an unknown URI data type: " + uri); + } + buffer_data = _parse_base64_uri(uri); + } else { // Relative path to an external image file. + uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. + buffer_data = FileAccess::get_file_as_array(uri); + ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); + } + + ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); + int byteLength = buffer["byteLength"]; + ERR_FAIL_COND_V(byteLength < buffer_data.size(), ERR_PARSE_ERROR); + state->buffers.push_back(buffer_data); + } + } + } + + print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); + + return OK; +} + +Error GLTFDocument::_encode_buffer_views(Ref state) { + Array buffers; + for (GLTFBufferViewIndex i = 0; i < state->buffer_views.size(); i++) { + Dictionary d; + + Ref buffer_view = state->buffer_views[i]; + + d["buffer"] = buffer_view->buffer; + d["byteLength"] = buffer_view->byte_length; + + d["byteOffset"] = buffer_view->byte_offset; + + if (buffer_view->byte_stride != -1) { + d["byteStride"] = buffer_view->byte_stride; + } + + // TODO Sparse + // d["target"] = buffer_view->indices; + + ERR_FAIL_COND_V(!d.has("buffer"), ERR_INVALID_DATA); + ERR_FAIL_COND_V(!d.has("byteLength"), ERR_INVALID_DATA); + buffers.push_back(d); + } + print_verbose("glTF: Total buffer views: " + itos(state->buffer_views.size())); + state->json["bufferViews"] = buffers; + return OK; +} + +Error GLTFDocument::_parse_buffer_views(Ref state) { + if (!state->json.has("bufferViews")) { + return OK; + } + const Array &buffers = state->json["bufferViews"]; + for (GLTFBufferViewIndex i = 0; i < buffers.size(); i++) { + const Dictionary &d = buffers[i]; + + Ref buffer_view; + buffer_view.instance(); + + ERR_FAIL_COND_V(!d.has("buffer"), ERR_PARSE_ERROR); + buffer_view->buffer = d["buffer"]; + ERR_FAIL_COND_V(!d.has("byteLength"), ERR_PARSE_ERROR); + buffer_view->byte_length = d["byteLength"]; + + if (d.has("byteOffset")) { + buffer_view->byte_offset = d["byteOffset"]; + } + + if (d.has("byteStride")) { + buffer_view->byte_stride = d["byteStride"]; + } + + if (d.has("target")) { + const int target = d["target"]; + buffer_view->indices = target == GLTFDocument::ELEMENT_ARRAY_BUFFER; + } + + state->buffer_views.push_back(buffer_view); + } + + print_verbose("glTF: Total buffer views: " + itos(state->buffer_views.size())); + + return OK; +} + +Error GLTFDocument::_encode_accessors(Ref state) { + Array accessors; + for (GLTFAccessorIndex i = 0; i < state->accessors.size(); i++) { + Dictionary d; + + Ref accessor = state->accessors[i]; + d["componentType"] = accessor->component_type; + d["count"] = accessor->count; + d["type"] = _get_accessor_type_name(accessor->type); + d["byteOffset"] = accessor->byte_offset; + d["normalized"] = accessor->normalized; + Array max; + max.resize(accessor->max.size()); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + max[max_i] = accessor->max[max_i]; + } + d["max"] = max; + Array min; + min.resize(accessor->min.size()); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + min[min_i] = accessor->min[min_i]; + } + d["min"] = min; + d["bufferView"] = accessor->buffer_view; //optional because it may be sparse... + + // Dictionary s; + // s["count"] = accessor->sparse_count; + // ERR_FAIL_COND_V(!s.has("count"), ERR_PARSE_ERROR); + + // s["indices"] = accessor->sparse_accessors; + // ERR_FAIL_COND_V(!s.has("indices"), ERR_PARSE_ERROR); + + // Dictionary si; + + // si["bufferView"] = accessor->sparse_indices_buffer_view; + + // ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR); + // si["componentType"] = accessor->sparse_indices_component_type; + + // if (si.has("byteOffset")) { + // si["byteOffset"] = accessor->sparse_indices_byte_offset; + // } + + // ERR_FAIL_COND_V(!si.has("componentType"), ERR_PARSE_ERROR); + // s["indices"] = si; + // Dictionary sv; + + // sv["bufferView"] = accessor->sparse_values_buffer_view; + // if (sv.has("byteOffset")) { + // sv["byteOffset"] = accessor->sparse_values_byte_offset; + // } + // ERR_FAIL_COND_V(!sv.has("bufferView"), ERR_PARSE_ERROR); + // s["values"] = sv; + // ERR_FAIL_COND_V(!s.has("values"), ERR_PARSE_ERROR); + // d["sparse"] = s; + accessors.push_back(d); + } + + state->json["accessors"] = accessors; + ERR_FAIL_COND_V(!state->json.has("accessors"), ERR_FILE_CORRUPT); + print_verbose("glTF: Total accessors: " + itos(state->accessors.size())); + + return OK; +} + +String GLTFDocument::_get_accessor_type_name(const GLTFDocument::GLTFType p_type) { + if (p_type == GLTFDocument::TYPE_SCALAR) { + return "SCALAR"; + } + if (p_type == GLTFDocument::TYPE_VEC2) { + return "VEC2"; + } + if (p_type == GLTFDocument::TYPE_VEC3) { + return "VEC3"; + } + if (p_type == GLTFDocument::TYPE_VEC4) { + return "VEC4"; + } + + if (p_type == GLTFDocument::TYPE_MAT2) { + return "MAT2"; + } + if (p_type == GLTFDocument::TYPE_MAT3) { + return "MAT3"; + } + if (p_type == GLTFDocument::TYPE_MAT4) { + return "MAT4"; + } + ERR_FAIL_V("SCALAR"); +} + +GLTFDocument::GLTFType GLTFDocument::_get_type_from_str(const String &p_string) { + if (p_string == "SCALAR") { + return GLTFDocument::TYPE_SCALAR; + } + + if (p_string == "VEC2") { + return GLTFDocument::TYPE_VEC2; + } + if (p_string == "VEC3") { + return GLTFDocument::TYPE_VEC3; + } + if (p_string == "VEC4") { + return GLTFDocument::TYPE_VEC4; + } + + if (p_string == "MAT2") { + return GLTFDocument::TYPE_MAT2; + } + if (p_string == "MAT3") { + return GLTFDocument::TYPE_MAT3; + } + if (p_string == "MAT4") { + return GLTFDocument::TYPE_MAT4; + } + + ERR_FAIL_V(GLTFDocument::TYPE_SCALAR); +} + +Error GLTFDocument::_parse_accessors(Ref state) { + if (!state->json.has("accessors")) { + return OK; + } + const Array &accessors = state->json["accessors"]; + for (GLTFAccessorIndex i = 0; i < accessors.size(); i++) { + const Dictionary &d = accessors[i]; + + Ref accessor; + accessor.instance(); + + ERR_FAIL_COND_V(!d.has("componentType"), ERR_PARSE_ERROR); + accessor->component_type = d["componentType"]; + ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR); + accessor->count = d["count"]; + ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); + accessor->type = _get_type_from_str(d["type"]); + + if (d.has("bufferView")) { + accessor->buffer_view = d["bufferView"]; //optional because it may be sparse... + } + + if (d.has("byteOffset")) { + accessor->byte_offset = d["byteOffset"]; + } + + if (d.has("normalized")) { + accessor->normalized = d["normalized"]; + } + + if (d.has("max")) { + Array max = d["max"]; + accessor->max.resize(max.size()); + PoolVector::Write max_write = accessor->max.write(); + for (int32_t max_i = 0; max_i < accessor->max.size(); max_i++) { + max_write[max_i] = max[max_i]; + } + } + + if (d.has("min")) { + Array min = d["min"]; + accessor->min.resize(min.size()); + PoolVector::Write min_write = accessor->min.write(); + for (int32_t min_i = 0; min_i < accessor->min.size(); min_i++) { + min_write[min_i] = min[min_i]; + } + } + + if (d.has("sparse")) { + //eeh.. + + const Dictionary &s = d["sparse"]; + + ERR_FAIL_COND_V(!s.has("count"), ERR_PARSE_ERROR); + accessor->sparse_count = s["count"]; + ERR_FAIL_COND_V(!s.has("indices"), ERR_PARSE_ERROR); + const Dictionary &si = s["indices"]; + + ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR); + accessor->sparse_indices_buffer_view = si["bufferView"]; + ERR_FAIL_COND_V(!si.has("componentType"), ERR_PARSE_ERROR); + accessor->sparse_indices_component_type = si["componentType"]; + + if (si.has("byteOffset")) { + accessor->sparse_indices_byte_offset = si["byteOffset"]; + } + + ERR_FAIL_COND_V(!s.has("values"), ERR_PARSE_ERROR); + const Dictionary &sv = s["values"]; + + ERR_FAIL_COND_V(!sv.has("bufferView"), ERR_PARSE_ERROR); + accessor->sparse_values_buffer_view = sv["bufferView"]; + if (sv.has("byteOffset")) { + accessor->sparse_values_byte_offset = sv["byteOffset"]; + } + } + + state->accessors.push_back(accessor); + } + + print_verbose("glTF: Total accessors: " + itos(state->accessors.size())); + + return OK; +} + +double GLTFDocument::_filter_number(double p_float) { + if (Math::is_nan(p_float)) { + return 0.0f; + } + return p_float; +} + +String GLTFDocument::_get_component_type_name(const uint32_t p_component) { + switch (p_component) { + case GLTFDocument::COMPONENT_TYPE_BYTE: + return "Byte"; + case GLTFDocument::COMPONENT_TYPE_UNSIGNED_BYTE: + return "UByte"; + case GLTFDocument::COMPONENT_TYPE_SHORT: + return "Short"; + case GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT: + return "UShort"; + case GLTFDocument::COMPONENT_TYPE_INT: + return "Int"; + case GLTFDocument::COMPONENT_TYPE_FLOAT: + return "Float"; + } + + return ""; +} + +String GLTFDocument::_get_type_name(const GLTFType p_component) { + static const char *names[] = { + "float", + "vec2", + "vec3", + "vec4", + "mat2", + "mat3", + "mat4" + }; + + return names[p_component]; +} + +Error GLTFDocument::_encode_buffer_view(Ref state, const double *src, const int count, const GLTFType type, const int component_type, const bool normalized, const int byte_offset, const bool for_vertex, GLTFBufferViewIndex &r_accessor) { + const int component_count_for_type[7] = { + 1, 2, 3, 4, 4, 9, 16 + }; + + const int component_count = component_count_for_type[type]; + const int component_size = _get_component_type_size(component_type); + ERR_FAIL_COND_V(component_size == 0, FAILED); + + int skip_every = 0; + int skip_bytes = 0; + //special case of alignments, as described in spec + switch (component_type) { + case COMPONENT_TYPE_BYTE: + case COMPONENT_TYPE_UNSIGNED_BYTE: { + if (type == TYPE_MAT2) { + skip_every = 2; + skip_bytes = 2; + } + if (type == TYPE_MAT3) { + skip_every = 3; + skip_bytes = 1; + } + } break; + case COMPONENT_TYPE_SHORT: + case COMPONENT_TYPE_UNSIGNED_SHORT: { + if (type == TYPE_MAT3) { + skip_every = 6; + skip_bytes = 4; + } + } break; + default: { + } + } + + Ref bv; + bv.instance(); + const uint32_t offset = bv->byte_offset = byte_offset; + Vector &gltf_buffer = state->buffers.write[0]; + + int stride = _get_component_type_size(component_type); + if (for_vertex && stride % 4) { + stride += 4 - (stride % 4); //according to spec must be multiple of 4 + } + //use to debug + print_verbose("glTF: encoding type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); + + print_verbose("glTF: encoding accessor offset " + itos(byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(gltf_buffer.size()) + " view len " + itos(bv->byte_length)); + + const int buffer_end = (stride * (count - 1)) + _get_component_type_size(component_type); + // TODO define bv->byte_stride + bv->byte_offset = gltf_buffer.size(); + + switch (component_type) { + case COMPONENT_TYPE_BYTE: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 128.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int8_t))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int8_t)); + bv->byte_length = buffer.size() * sizeof(int8_t); + } break; + case COMPONENT_TYPE_UNSIGNED_BYTE: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 255.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + gltf_buffer.append_array(buffer); + bv->byte_length = buffer.size() * sizeof(uint8_t); + } break; + case COMPONENT_TYPE_SHORT: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 32768.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int16_t))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int16_t)); + bv->byte_length = buffer.size() * sizeof(int16_t); + } break; + case COMPONENT_TYPE_UNSIGNED_SHORT: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 65535.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint16_t))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint16_t)); + bv->byte_length = buffer.size() * sizeof(uint16_t); + } break; + case COMPONENT_TYPE_INT: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + buffer.write[dst_i] = d; + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int32_t))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int32_t)); + bv->byte_length = buffer.size() * sizeof(int32_t); + } break; + case COMPONENT_TYPE_FLOAT: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + buffer.write[dst_i] = d; + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(float))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(float)); + bv->byte_length = buffer.size() * sizeof(float); + } break; + } + ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_INVALID_DATA); + + ERR_FAIL_COND_V((int)(offset + buffer_end) > gltf_buffer.size(), ERR_INVALID_DATA); + r_accessor = bv->buffer = state->buffer_views.size(); + state->buffer_views.push_back(bv); + return OK; +} + +Error GLTFDocument::_decode_buffer_view(Ref state, double *dst, const GLTFBufferViewIndex p_buffer_view, const int skip_every, const int skip_bytes, const int element_size, const int count, const GLTFType type, const int component_count, const int component_type, const int component_size, const bool normalized, const int byte_offset, const bool for_vertex) { + const Ref bv = state->buffer_views[p_buffer_view]; + + int stride = element_size; + if (bv->byte_stride != -1) { + stride = bv->byte_stride; + } + if (for_vertex && stride % 4) { + stride += 4 - (stride % 4); //according to spec must be multiple of 4 + } + + ERR_FAIL_INDEX_V(bv->buffer, state->buffers.size(), ERR_PARSE_ERROR); + + const uint32_t offset = bv->byte_offset + byte_offset; + Vector buffer = state->buffers[bv->buffer]; //copy on write, so no performance hit + const uint8_t *bufptr = buffer.ptr(); + + //use to debug + print_verbose("glTF: type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); + print_verbose("glTF: accessor offset " + itos(byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv->byte_length)); + + const int buffer_end = (stride * (count - 1)) + element_size; + ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_PARSE_ERROR); + + ERR_FAIL_COND_V((int)(offset + buffer_end) > buffer.size(), ERR_PARSE_ERROR); + + //fill everything as doubles + + for (int i = 0; i < count; i++) { + const uint8_t *src = &bufptr[offset + i * stride]; + + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + src += skip_bytes; + } + + double d = 0; + + switch (component_type) { + case COMPONENT_TYPE_BYTE: { + int8_t b = int8_t(*src); + if (normalized) { + d = (double(b) / 128.0); + } else { + d = double(b); + } + } break; + case COMPONENT_TYPE_UNSIGNED_BYTE: { + uint8_t b = *src; + if (normalized) { + d = (double(b) / 255.0); + } else { + d = double(b); + } + } break; + case COMPONENT_TYPE_SHORT: { + int16_t s = *(int16_t *)src; + if (normalized) { + d = (double(s) / 32768.0); + } else { + d = double(s); + } + } break; + case COMPONENT_TYPE_UNSIGNED_SHORT: { + uint16_t s = *(uint16_t *)src; + if (normalized) { + d = (double(s) / 65535.0); + } else { + d = double(s); + } + } break; + case COMPONENT_TYPE_INT: { + d = *(int *)src; + } break; + case COMPONENT_TYPE_FLOAT: { + d = *(float *)src; + } break; + } + + *dst++ = d; + src += component_size; + } + } + + return OK; +} + +int GLTFDocument::_get_component_type_size(const int component_type) { + switch (component_type) { + case COMPONENT_TYPE_BYTE: + case COMPONENT_TYPE_UNSIGNED_BYTE: + return 1; + break; + case COMPONENT_TYPE_SHORT: + case COMPONENT_TYPE_UNSIGNED_SHORT: + return 2; + break; + case COMPONENT_TYPE_INT: + case COMPONENT_TYPE_FLOAT: + return 4; + break; + default: { + ERR_FAIL_V(0); + } + } + return 0; +} + +Vector GLTFDocument::_decode_accessor(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + //spec, for reference: + //https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + + ERR_FAIL_INDEX_V(p_accessor, state->accessors.size(), Vector()); + + const Ref a = state->accessors[p_accessor]; + + const int component_count_for_type[7] = { + 1, 2, 3, 4, 4, 9, 16 + }; + + const int component_count = component_count_for_type[a->type]; + const int component_size = _get_component_type_size(a->component_type); + ERR_FAIL_COND_V(component_size == 0, Vector()); + int element_size = component_count * component_size; + + int skip_every = 0; + int skip_bytes = 0; + //special case of alignments, as described in spec + switch (a->component_type) { + case COMPONENT_TYPE_BYTE: + case COMPONENT_TYPE_UNSIGNED_BYTE: { + if (a->type == TYPE_MAT2) { + skip_every = 2; + skip_bytes = 2; + element_size = 8; //override for this case + } + if (a->type == TYPE_MAT3) { + skip_every = 3; + skip_bytes = 1; + element_size = 12; //override for this case + } + } break; + case COMPONENT_TYPE_SHORT: + case COMPONENT_TYPE_UNSIGNED_SHORT: { + if (a->type == TYPE_MAT3) { + skip_every = 6; + skip_bytes = 4; + element_size = 16; //override for this case + } + } break; + default: { + } + } + + Vector dst_buffer; + dst_buffer.resize(component_count * a->count); + double *dst = dst_buffer.ptrw(); + + if (a->buffer_view >= 0) { + ERR_FAIL_INDEX_V(a->buffer_view, state->buffer_views.size(), Vector()); + + const Error err = _decode_buffer_view(state, dst, a->buffer_view, skip_every, skip_bytes, element_size, a->count, a->type, component_count, a->component_type, component_size, a->normalized, a->byte_offset, p_for_vertex); + if (err != OK) { + return Vector(); + } + } else { + //fill with zeros, as bufferview is not defined. + for (int i = 0; i < (a->count * component_count); i++) { + dst_buffer.write[i] = 0; + } + } + + if (a->sparse_count > 0) { + // I could not find any file using this, so this code is so far untested + Vector indices; + indices.resize(a->sparse_count); + const int indices_component_size = _get_component_type_size(a->sparse_indices_component_type); + + Error err = _decode_buffer_view(state, indices.ptrw(), a->sparse_indices_buffer_view, 0, 0, indices_component_size, a->sparse_count, TYPE_SCALAR, 1, a->sparse_indices_component_type, indices_component_size, false, a->sparse_indices_byte_offset, false); + if (err != OK) { + return Vector(); + } + + Vector data; + data.resize(component_count * a->sparse_count); + err = _decode_buffer_view(state, data.ptrw(), a->sparse_values_buffer_view, skip_every, skip_bytes, element_size, a->sparse_count, a->type, component_count, a->component_type, component_size, a->normalized, a->sparse_values_byte_offset, p_for_vertex); + if (err != OK) { + return Vector(); + } + + for (int i = 0; i < indices.size(); i++) { + const int write_offset = int(indices[i]) * component_count; + + for (int j = 0; j < component_count; j++) { + dst[write_offset + j] = data[i * component_count + j]; + } + } + } + + return dst_buffer; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 1; + const int ret_size = p_attribs.size(); + Vector attribs; + attribs.resize(ret_size); + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + attribs.write[i] = Math::stepify(p_attribs[i], 1.0); + if (i == 0) { + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = attribs[(i * element_count) + type_i]; + type_min.write[type_i] = attribs[(i * element_count) + type_i]; + } + } + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = MAX(attribs[(i * element_count) + type_i], type_max[type_i]); + type_min.write[type_i] = MIN(attribs[(i * element_count) + type_i], type_min[type_i]); + type_max.write[type_i] = _filter_number(type_max.write[type_i]); + type_min.write[type_i] = _filter_number(type_min.write[type_i]); + } + } + + ERR_FAIL_COND_V(attribs.size() == 0, -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_SCALAR; + const int component_type = GLTFDocument::COMPONENT_TYPE_INT; + + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->min = min; + accessor->normalized = false; + accessor->count = ret_size; + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +Vector GLTFDocument::_decode_accessor_as_ints(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size(); + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = int(attribs_ptr[i]); + } + } + return ret; +} + +Vector GLTFDocument::_decode_accessor_as_floats(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size(); + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = float(attribs_ptr[i]); + } + } + return ret; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 2; + + const int ret_size = p_attribs.size() * element_count; + Vector attribs; + attribs.resize(ret_size); + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + + for (int i = 0; i < p_attribs.size(); i++) { + Vector2 attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.y, CMP_NORMALIZE_TOLERANCE); + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC2; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->normalized = false; + accessor->count = p_attribs.size(); + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 4; + Vector attribs; + attribs.resize(ret_size); + + const int element_count = 4; + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.r, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.g, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(attrib.b, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 3] = Math::stepify(attrib.a, CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC4; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->normalized = false; + accessor->count = p_attribs.size(); + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +void GLTFDocument::_calc_accessor_min_max(int i, const int element_count, Vector &type_max, Vector attribs, Vector &type_min) { + if (i == 0) { + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = attribs[(i * element_count) + type_i]; + type_min.write[type_i] = attribs[(i * element_count) + type_i]; + } + } + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = MAX(attribs[(i * element_count) + type_i], type_max[type_i]); + type_min.write[type_i] = MIN(attribs[(i * element_count) + type_i], type_min[type_i]); + type_max.write[type_i] = _filter_number(type_max.write[type_i]); + type_min.write[type_i] = _filter_number(type_min.write[type_i]); + } +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 4; + Vector attribs; + attribs.resize(ret_size); + + const int element_count = 4; + + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.r, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.g, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(attrib.b, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 3] = Math::stepify(attrib.a, CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC4; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->normalized = false; + accessor->count = p_attribs.size(); + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + + const int element_count = 4; + const int ret_size = p_attribs.size() * element_count; + Vector attribs; + attribs.resize(ret_size); + + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.r, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.g, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(attrib.b, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 3] = Math::stepify(attrib.a, CMP_NORMALIZE_TOLERANCE); + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC4; + const int component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT; + + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->normalized = false; + accessor->count = p_attribs.size(); + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quats(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 4; + + const int ret_size = p_attribs.size() * element_count; + Vector attribs; + attribs.resize(ret_size); + + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Quat quat = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(quat.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(quat.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(quat.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 3] = Math::stepify(quat.w, CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC4; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->normalized = false; + accessor->count = p_attribs.size(); + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +Vector GLTFDocument::_decode_accessor_as_vec2(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + ERR_FAIL_COND_V(attribs.size() % 2 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 2; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Vector2(attribs_ptr[i * 2 + 0], attribs_ptr[i * 2 + 1]); + } + } + return ret; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 1; + const int ret_size = p_attribs.size(); + Vector attribs; + attribs.resize(ret_size); + + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + + for (int i = 0; i < p_attribs.size(); i++) { + attribs.write[i] = Math::stepify(p_attribs[i], CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(!attribs.size(), -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_SCALAR; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->normalized = false; + accessor->count = ret_size; + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 3; + const int ret_size = p_attribs.size() * element_count; + Vector attribs; + attribs.resize(ret_size); + + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Vector3 attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(attrib.z, CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC3; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->normalized = false; + accessor->count = p_attribs.size(); + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 16; + const int ret_size = p_attribs.size() * element_count; + Vector attribs; + attribs.resize(ret_size); + + Vector type_max; + type_max.resize(element_count); + Vector type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Transform attrib = p_attribs[i]; + Basis basis = attrib.get_basis(); + Vector3 axis_0 = basis.get_axis(Vector3::AXIS_X); + + attribs.write[i * element_count + 0] = Math::stepify(axis_0.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 1] = Math::stepify(axis_0.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 2] = Math::stepify(axis_0.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 3] = 0.0; + + Vector3 axis_1 = basis.get_axis(Vector3::AXIS_Y); + attribs.write[i * element_count + 4] = Math::stepify(axis_1.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 5] = Math::stepify(axis_1.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 6] = Math::stepify(axis_1.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 7] = 0.0; + + Vector3 axis_2 = basis.get_axis(Vector3::AXIS_Z); + attribs.write[i * element_count + 8] = Math::stepify(axis_2.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 9] = Math::stepify(axis_2.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 10] = Math::stepify(axis_2.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 11] = 0.0; + + Vector3 origin = attrib.get_origin(); + attribs.write[i * element_count + 12] = Math::stepify(origin.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 13] = Math::stepify(origin.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 14] = Math::stepify(origin.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 15] = 1.0; + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_MAT4; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + PoolVector max; + max.resize(type_max.size()); + PoolVector::Write write_max = max.write(); + for (int32_t max_i = 0; max_i < max.size(); max_i++) { + write_max[max_i] = type_max[max_i]; + } + accessor->max = max; + PoolVector min; + min.resize(type_min.size()); + PoolVector::Write write_min = min.write(); + for (int32_t min_i = 0; min_i < min.size(); min_i++) { + write_min[min_i] = type_min[min_i]; + } + accessor->normalized = false; + accessor->count = p_attribs.size(); + accessor->type = type; + accessor->component_type = component_type; + accessor->byte_offset = 0; + Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +Vector GLTFDocument::_decode_accessor_as_vec3(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + ERR_FAIL_COND_V(attribs.size() % 3 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 3; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Vector3(attribs_ptr[i * 3 + 0], attribs_ptr[i * 3 + 1], attribs_ptr[i * 3 + 2]); + } + } + return ret; +} + +Vector GLTFDocument::_decode_accessor_as_color(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + const int type = state->accessors[p_accessor]->type; + ERR_FAIL_COND_V(!(type == TYPE_VEC3 || type == TYPE_VEC4), ret); + int vec_len = 3; + if (type == TYPE_VEC4) { + vec_len = 4; + } + + ERR_FAIL_COND_V(attribs.size() % vec_len != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / vec_len; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Color(attribs_ptr[i * vec_len + 0], attribs_ptr[i * vec_len + 1], attribs_ptr[i * vec_len + 2], vec_len == 4 ? attribs_ptr[i * 4 + 3] : 1.0); + } + } + return ret; +} +Vector GLTFDocument::_decode_accessor_as_quat(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 4; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Quat(attribs_ptr[i * 4 + 0], attribs_ptr[i * 4 + 1], attribs_ptr[i * 4 + 2], attribs_ptr[i * 4 + 3]).normalized(); + } + } + return ret; +} +Vector GLTFDocument::_decode_accessor_as_xform2d(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); + ret.resize(attribs.size() / 4); + for (int i = 0; i < ret.size(); i++) { + ret.write[i][0] = Vector2(attribs[i * 4 + 0], attribs[i * 4 + 1]); + ret.write[i][1] = Vector2(attribs[i * 4 + 2], attribs[i * 4 + 3]); + } + return ret; +} + +Vector GLTFDocument::_decode_accessor_as_basis(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + ERR_FAIL_COND_V(attribs.size() % 9 != 0, ret); + ret.resize(attribs.size() / 9); + for (int i = 0; i < ret.size(); i++) { + ret.write[i].set_axis(0, Vector3(attribs[i * 9 + 0], attribs[i * 9 + 1], attribs[i * 9 + 2])); + ret.write[i].set_axis(1, Vector3(attribs[i * 9 + 3], attribs[i * 9 + 4], attribs[i * 9 + 5])); + ret.write[i].set_axis(2, Vector3(attribs[i * 9 + 6], attribs[i * 9 + 7], attribs[i * 9 + 8])); + } + return ret; +} + +Vector GLTFDocument::_decode_accessor_as_xform(Ref state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) { + return ret; + } + + ERR_FAIL_COND_V(attribs.size() % 16 != 0, ret); + ret.resize(attribs.size() / 16); + for (int i = 0; i < ret.size(); i++) { + ret.write[i].basis.set_axis(0, Vector3(attribs[i * 16 + 0], attribs[i * 16 + 1], attribs[i * 16 + 2])); + ret.write[i].basis.set_axis(1, Vector3(attribs[i * 16 + 4], attribs[i * 16 + 5], attribs[i * 16 + 6])); + ret.write[i].basis.set_axis(2, Vector3(attribs[i * 16 + 8], attribs[i * 16 + 9], attribs[i * 16 + 10])); + ret.write[i].set_origin(Vector3(attribs[i * 16 + 12], attribs[i * 16 + 13], attribs[i * 16 + 14])); + } + return ret; +} + +Error GLTFDocument::_serialize_meshes(Ref state) { + Array meshes; + for (GLTFMeshIndex gltf_mesh_i = 0; gltf_mesh_i < state->meshes.size(); gltf_mesh_i++) { + print_verbose("glTF: Serializing mesh: " + itos(gltf_mesh_i)); + Ref import_mesh = state->meshes.write[gltf_mesh_i]->get_mesh(); + if (import_mesh.is_null()) { + continue; + } + Array primitives; + Array targets; + Dictionary gltf_mesh; + Array target_names; + Array weights; + for (int surface_i = 0; surface_i < import_mesh->get_surface_count(); surface_i++) { + Dictionary primitive; + Mesh::PrimitiveType primitive_type = import_mesh->surface_get_primitive_type(surface_i); + switch (primitive_type) { + case Mesh::PRIMITIVE_POINTS: { + primitive["mode"] = 0; + break; + } + case Mesh::PRIMITIVE_LINES: { + primitive["mode"] = 1; + break; + } + // case Mesh::PRIMITIVE_LINE_LOOP: { + // primitive["mode"] = 2; + // break; + // } + case Mesh::PRIMITIVE_LINE_STRIP: { + primitive["mode"] = 3; + break; + } + case Mesh::PRIMITIVE_TRIANGLES: { + primitive["mode"] = 4; + break; + } + case Mesh::PRIMITIVE_TRIANGLE_STRIP: { + primitive["mode"] = 5; + break; + } + // case Mesh::PRIMITIVE_TRIANGLE_FAN: { + // primitive["mode"] = 6; + // break; + // } + default: { + ERR_FAIL_V(FAILED); + } + } + + Array array = import_mesh->surface_get_arrays(surface_i); + Dictionary attributes; + { + Vector a = array[Mesh::ARRAY_VERTEX]; + ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA); + attributes["POSITION"] = _encode_accessor_as_vec3(state, a, true); + } + { + Vector a = array[Mesh::ARRAY_TANGENT]; + if (a.size()) { + const int ret_size = a.size() / 4; + Vector attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + Color out; + out.r = a[(i * 4) + 0]; + out.g = a[(i * 4) + 1]; + out.b = a[(i * 4) + 2]; + out.a = a[(i * 4) + 3]; + attribs.write[i] = out; + } + attributes["TANGENT"] = _encode_accessor_as_color(state, attribs, true); + } + } + { + Vector a = array[Mesh::ARRAY_NORMAL]; + if (a.size()) { + const int ret_size = a.size(); + Vector attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + attribs.write[i] = Vector3(a[i]).normalized(); + } + attributes["NORMAL"] = _encode_accessor_as_vec3(state, attribs, true); + } + } + { + Vector a = array[Mesh::ARRAY_TEX_UV]; + if (a.size()) { + attributes["TEXCOORD_0"] = _encode_accessor_as_vec2(state, a, true); + } + } + { + Vector a = array[Mesh::ARRAY_TEX_UV2]; + if (a.size()) { + attributes["TEXCOORD_1"] = _encode_accessor_as_vec2(state, a, true); + } + } + { + Vector a = array[Mesh::ARRAY_COLOR]; + if (a.size()) { + attributes["COLOR_0"] = _encode_accessor_as_color(state, a, true); + } + } + Map joint_i_to_bone_i; + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + GLTFSkinIndex skin_i = -1; + if (state->nodes[node_i]->mesh == gltf_mesh_i) { + skin_i = state->nodes[node_i]->skin; + } + if (skin_i != -1) { + joint_i_to_bone_i = state->skins[skin_i]->joint_i_to_bone_i; + break; + } + } + { + const Array &a = array[Mesh::ARRAY_BONES]; + const Vector &vertex_array = array[Mesh::ARRAY_VERTEX]; + if ((a.size() / JOINT_GROUP_SIZE) == vertex_array.size()) { + const int ret_size = a.size() / JOINT_GROUP_SIZE; + Vector attribs; + attribs.resize(ret_size); + { + for (int array_i = 0; array_i < attribs.size(); array_i++) { + int32_t joint_0 = a[(array_i * JOINT_GROUP_SIZE) + 0]; + int32_t joint_1 = a[(array_i * JOINT_GROUP_SIZE) + 1]; + int32_t joint_2 = a[(array_i * JOINT_GROUP_SIZE) + 2]; + int32_t joint_3 = a[(array_i * JOINT_GROUP_SIZE) + 3]; + attribs.write[array_i] = Color(joint_0, joint_1, joint_2, joint_3); + } + } + attributes["JOINTS_0"] = _encode_accessor_as_joints(state, attribs, true); + } + ERR_FAIL_COND_V((a.size() / (JOINT_GROUP_SIZE * 2)) >= vertex_array.size(), FAILED); + } + { + const Array &a = array[Mesh::ARRAY_WEIGHTS]; + const Vector &vertex_array = array[Mesh::ARRAY_VERTEX]; + if ((a.size() / JOINT_GROUP_SIZE) == vertex_array.size()) { + const int ret_size = a.size() / JOINT_GROUP_SIZE; + Vector attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + attribs.write[i] = Color(a[(i * JOINT_GROUP_SIZE) + 0], a[(i * JOINT_GROUP_SIZE) + 1], a[(i * JOINT_GROUP_SIZE) + 2], a[(i * JOINT_GROUP_SIZE) + 3]); + } + attributes["WEIGHTS_0"] = _encode_accessor_as_weights(state, attribs, true); + } else if ((a.size() / (JOINT_GROUP_SIZE * 2)) >= vertex_array.size()) { + int32_t vertex_count = vertex_array.size(); + Vector weights_0; + weights_0.resize(vertex_count); + Vector weights_1; + weights_1.resize(vertex_count); + int32_t weights_8_count = JOINT_GROUP_SIZE * 2; + for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + Color weight_0; + weight_0.r = a[vertex_i * weights_8_count + 0]; + weight_0.g = a[vertex_i * weights_8_count + 1]; + weight_0.b = a[vertex_i * weights_8_count + 2]; + weight_0.a = a[vertex_i * weights_8_count + 3]; + weights_0.write[vertex_i] = weight_0; + Color weight_1; + weight_1.r = a[vertex_i * weights_8_count + 4]; + weight_1.g = a[vertex_i * weights_8_count + 5]; + weight_1.b = a[vertex_i * weights_8_count + 6]; + weight_1.a = a[vertex_i * weights_8_count + 7]; + weights_1.write[vertex_i] = weight_1; + } + attributes["WEIGHTS_0"] = _encode_accessor_as_weights(state, weights_0, true); + attributes["WEIGHTS_1"] = _encode_accessor_as_weights(state, weights_1, true); + } + } + { + Vector mesh_indices = array[Mesh::ARRAY_INDEX]; + if (mesh_indices.size()) { + if (primitive_type == Mesh::PRIMITIVE_TRIANGLES) { + //swap around indices, convert ccw to cw for front face + const int is = mesh_indices.size(); + for (int k = 0; k < is; k += 3) { + SWAP(mesh_indices.write[k + 0], mesh_indices.write[k + 2]); + } + } + primitive["indices"] = _encode_accessor_as_ints(state, mesh_indices, true); + } else { + if (primitive_type == Mesh::PRIMITIVE_TRIANGLES) { + //generate indices because they need to be swapped for CW/CCW + const Vector &vertices = array[Mesh::ARRAY_VERTEX]; + Ref st; + st.instance(); + st->create_from_triangle_arrays(array); + st->index(); + Vector generated_indices = st->commit_to_arrays()[Mesh::ARRAY_INDEX]; + const int vs = vertices.size(); + generated_indices.resize(vs); + { + for (int k = 0; k < vs; k += 3) { + generated_indices.write[k] = k; + generated_indices.write[k + 1] = k + 2; + generated_indices.write[k + 2] = k + 1; + } + } + primitive["indices"] = _encode_accessor_as_ints(state, generated_indices, true); + } + } + } + + primitive["attributes"] = attributes; + + //blend shapes + print_verbose("glTF: Mesh has targets"); + if (import_mesh->get_blend_shape_count()) { + ArrayMesh::BlendShapeMode shape_mode = import_mesh->get_blend_shape_mode(); + Array array_morphs = import_mesh->surface_get_blend_shape_arrays(surface_i); + for (int morph_i = 0; morph_i < array_morphs.size(); morph_i++) { + Array array_morph = array_morphs[morph_i]; + target_names.push_back(import_mesh->get_blend_shape_name(morph_i)); + Dictionary t; + Vector varr = array_morph[Mesh::ARRAY_VERTEX]; + Array mesh_arrays = import_mesh->surface_get_arrays(surface_i); + if (varr.size()) { + Vector src_varr = array[Mesh::ARRAY_VERTEX]; + if (shape_mode == ArrayMesh::BlendShapeMode::BLEND_SHAPE_MODE_NORMALIZED) { + const int max_idx = src_varr.size(); + for (int blend_i = 0; blend_i < max_idx; blend_i++) { + varr.write[blend_i] = Vector3(varr[blend_i]) - src_varr[blend_i]; + } + } + + t["POSITION"] = _encode_accessor_as_vec3(state, varr, true); + } + + Vector narr = array_morph[Mesh::ARRAY_NORMAL]; + if (varr.size()) { + t["NORMAL"] = _encode_accessor_as_vec3(state, narr, true); + } + Vector tarr = array_morph[Mesh::ARRAY_TANGENT]; + if (tarr.size()) { + const int ret_size = tarr.size() / 4; + Vector attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + Color tangent; + tangent.r = tarr[(i * 4) + 0]; + tangent.g = tarr[(i * 4) + 1]; + tangent.b = tarr[(i * 4) + 2]; + tangent.a = tarr[(i * 4) + 3]; + } + t["TANGENT"] = _encode_accessor_as_color(state, attribs, true); + } + targets.push_back(t); + } + } + + Ref mat = import_mesh->surface_get_material(surface_i); + if (mat.is_valid()) { + Map, GLTFMaterialIndex>::Element *material_cache_i = state->material_cache.find(mat); + if (material_cache_i && material_cache_i->get() != -1) { + primitive["material"] = material_cache_i->get(); + } else { + GLTFMaterialIndex mat_i = state->materials.size(); + state->materials.push_back(mat); + primitive["material"] = mat_i; + state->material_cache.insert(mat, mat_i); + } + } + + if (targets.size()) { + primitive["targets"] = targets; + } + + primitives.push_back(primitive); + } + + Dictionary e; + e["targetNames"] = target_names; + + for (int j = 0; j < target_names.size(); j++) { + real_t weight = 0.0; + if (j < state->meshes.write[gltf_mesh_i]->get_blend_weights().size()) { + weight = state->meshes.write[gltf_mesh_i]->get_blend_weights()[j]; + } + weights.push_back(weight); + } + if (weights.size()) { + gltf_mesh["weights"] = weights; + } + + ERR_FAIL_COND_V(target_names.size() != weights.size(), FAILED); + + gltf_mesh["extras"] = e; + + gltf_mesh["primitives"] = primitives; + + meshes.push_back(gltf_mesh); + } + + state->json["meshes"] = meshes; + print_verbose("glTF: Total meshes: " + itos(meshes.size())); + + return OK; +} + +Error GLTFDocument::_parse_meshes(Ref state) { + if (!state->json.has("meshes")) { + return OK; + } + + Array meshes = state->json["meshes"]; + for (GLTFMeshIndex i = 0; i < meshes.size(); i++) { + print_verbose("glTF: Parsing mesh: " + itos(i)); + Dictionary d = meshes[i]; + + Ref mesh; + mesh.instance(); + bool has_vertex_color = false; + + ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); + + Array primitives = d["primitives"]; + const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + Ref import_mesh; + import_mesh.instance(); + String mesh_name = "mesh"; + if (d.has("name") && !String(d["name"]).empty()) { + mesh_name = d["name"]; + } + import_mesh->set_name(_gen_unique_name(state, vformat("%s_%s", state->scene_name, mesh_name))); + + for (int j = 0; j < primitives.size(); j++) { + Dictionary p = primitives[j]; + + Array array; + array.resize(Mesh::ARRAY_MAX); + + ERR_FAIL_COND_V(!p.has("attributes"), ERR_PARSE_ERROR); + + Dictionary a = p["attributes"]; + + Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; + if (p.has("mode")) { + const int mode = p["mode"]; + ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT); + static const Mesh::PrimitiveType primitives2[7] = { + Mesh::PRIMITIVE_POINTS, + Mesh::PRIMITIVE_LINES, + Mesh::PRIMITIVE_LINES, //loop not supported, should ce converted + Mesh::PRIMITIVE_LINES, + Mesh::PRIMITIVE_TRIANGLES, + Mesh::PRIMITIVE_TRIANGLE_STRIP, + Mesh::PRIMITIVE_TRIANGLES, //fan not supported, should be converted +#ifndef _MSC_VER +// #warning line loop and triangle fan are not supported and need to be converted to lines and triangles +#endif + + }; + + primitive = primitives2[mode]; + } + + ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR); + if (a.has("POSITION")) { + array[Mesh::ARRAY_VERTEX] = _decode_accessor_as_vec3(state, a["POSITION"], true); + } + if (a.has("NORMAL")) { + array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); + } + if (a.has("TANGENT")) { + array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(state, a["TANGENT"], true); + } + if (a.has("TEXCOORD_0")) { + array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(state, a["TEXCOORD_0"], true); + } + if (a.has("TEXCOORD_1")) { + array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(state, a["TEXCOORD_1"], true); + } + if (a.has("COLOR_0")) { + array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(state, a["COLOR_0"], true); + has_vertex_color = true; + } + if (a.has("JOINTS_0") && !a.has("JOINTS_1")) { + array[Mesh::ARRAY_BONES] = _decode_accessor_as_ints(state, a["JOINTS_0"], true); + } + ERR_CONTINUE(a.has("JOINTS_0") && a.has("JOINTS_1")); + if (a.has("WEIGHTS_0") && !a.has("WEIGHTS_1")) { + Vector weights = _decode_accessor_as_floats(state, a["WEIGHTS_0"], true); + { //gltf does not seem to normalize the weights for some reason.. + int wc = weights.size(); + float *w = weights.ptrw(); + + for (int k = 0; k < wc; k += 4) { + float total = 0.0; + total += w[k + 0]; + total += w[k + 1]; + total += w[k + 2]; + total += w[k + 3]; + if (total > 0.0) { + w[k + 0] /= total; + w[k + 1] /= total; + w[k + 2] /= total; + w[k + 3] /= total; + } + } + } + array[Mesh::ARRAY_WEIGHTS] = weights; + } + ERR_CONTINUE(a.has("WEIGHTS_0") && a.has("WEIGHTS_1")); + + if (p.has("indices")) { + Vector indices = _decode_accessor_as_ints(state, p["indices"], false); + + if (primitive == Mesh::PRIMITIVE_TRIANGLES) { + //swap around indices, convert ccw to cw for front face + + const int is = indices.size(); + int *w = indices.ptrw(); + for (int k = 0; k < is; k += 3) { + SWAP(w[k + 1], w[k + 2]); + } + } + array[Mesh::ARRAY_INDEX] = indices; + + } else if (primitive == Mesh::PRIMITIVE_TRIANGLES) { + //generate indices because they need to be swapped for CW/CCW + const Vector &vertices = array[Mesh::ARRAY_VERTEX]; + ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR); + Vector indices; + const int vs = vertices.size(); + indices.resize(vs); + { + int *w = indices.ptrw(); + for (int k = 0; k < vs; k += 3) { + w[k] = k; + w[k + 1] = k + 2; + w[k + 2] = k + 1; + } + } + array[Mesh::ARRAY_INDEX] = indices; + } + + bool generate_tangents = (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("TEXCOORD_0") && a.has("NORMAL")); + + if (generate_tangents) { + //must generate mikktspace tangents.. ergh.. + Ref st; + st.instance(); + st->create_from_triangle_arrays(array); + st->generate_tangents(); + array = st->commit_to_arrays(); + } + + Array morphs; + //blend shapes + if (p.has("targets")) { + print_verbose("glTF: Mesh has targets"); + const Array &targets = p["targets"]; + + //ideally BLEND_SHAPE_MODE_RELATIVE since gltf2 stores in displacement + //but it could require a larger refactor? + import_mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); + + if (j == 0) { + const Array &target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array(); + for (int k = 0; k < targets.size(); k++) { + const String name = k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k); + import_mesh->add_blend_shape(name); + } + } + + for (int k = 0; k < targets.size(); k++) { + const Dictionary &t = targets[k]; + + Array array_copy; + array_copy.resize(Mesh::ARRAY_MAX); + + for (int l = 0; l < Mesh::ARRAY_MAX; l++) { + array_copy[l] = array[l]; + } + + array_copy[Mesh::ARRAY_INDEX] = Variant(); + + if (t.has("POSITION")) { + Vector varr = _decode_accessor_as_vec3(state, t["POSITION"], true); + const Vector src_varr = array[Mesh::ARRAY_VERTEX]; + const int size = src_varr.size(); + ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); + { + const int max_idx = varr.size(); + varr.resize(size); + + Vector3 *w_varr = varr.ptrw(); + const Vector3 *r_varr = varr.ptr(); + const Vector3 *r_src_varr = src_varr.ptr(); + for (int l = 0; l < size; l++) { + if (l < max_idx) { + w_varr[l] = r_varr[l] + r_src_varr[l]; + } else { + w_varr[l] = r_src_varr[l]; + } + } + } + array_copy[Mesh::ARRAY_VERTEX] = varr; + } + if (t.has("NORMAL")) { + Vector narr = _decode_accessor_as_vec3(state, t["NORMAL"], true); + const Vector src_narr = array[Mesh::ARRAY_NORMAL]; + int size = src_narr.size(); + ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); + { + int max_idx = narr.size(); + narr.resize(size); + + Vector3 *w_narr = narr.ptrw(); + const Vector3 *r_narr = narr.ptr(); + const Vector3 *r_src_narr = src_narr.ptr(); + for (int l = 0; l < size; l++) { + if (l < max_idx) { + w_narr[l] = r_narr[l] + r_src_narr[l]; + } else { + w_narr[l] = r_src_narr[l]; + } + } + } + array_copy[Mesh::ARRAY_NORMAL] = narr; + } + if (t.has("TANGENT")) { + const Vector tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true); + const Vector src_tangents = array[Mesh::ARRAY_TANGENT]; + ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR); + + Vector tangents_v4; + + { + int max_idx = tangents_v3.size(); + + int size4 = src_tangents.size(); + tangents_v4.resize(size4); + float *w4 = tangents_v4.ptrw(); + + const Vector3 *r3 = tangents_v3.ptr(); + const float *r4 = src_tangents.ptr(); + + for (int l = 0; l < size4 / 4; l++) { + if (l < max_idx) { + w4[l * 4 + 0] = r3[l].x + r4[l * 4 + 0]; + w4[l * 4 + 1] = r3[l].y + r4[l * 4 + 1]; + w4[l * 4 + 2] = r3[l].z + r4[l * 4 + 2]; + } else { + w4[l * 4 + 0] = r4[l * 4 + 0]; + w4[l * 4 + 1] = r4[l * 4 + 1]; + w4[l * 4 + 2] = r4[l * 4 + 2]; + } + w4[l * 4 + 3] = r4[l * 4 + 3]; //copy flip value + } + } + + array_copy[Mesh::ARRAY_TANGENT] = tangents_v4; + } + + if (generate_tangents) { + Ref st; + st.instance(); + st->create_from_triangle_arrays(array_copy); + st->deindex(); + st->generate_tangents(); + array_copy = st->commit_to_arrays(); + } + + morphs.push_back(array_copy); + } + } + + //just add it + + Ref mat; + if (p.has("material")) { + const int material = p["material"]; + ERR_FAIL_INDEX_V(material, state->materials.size(), ERR_FILE_CORRUPT); + Ref mat3d = state->materials[material]; + if (has_vertex_color) { + mat3d->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + } + mat = mat3d; + + } else if (has_vertex_color) { + Ref mat3d; + mat3d.instance(); + mat3d->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + mat = mat3d; + } + int32_t mat_idx = import_mesh->get_surface_count(); + import_mesh->add_surface_from_arrays(primitive, array, morphs); + import_mesh->surface_set_material(mat_idx, mat); + } + + Vector blend_weights; + blend_weights.resize(import_mesh->get_blend_shape_count()); + for (int32_t weight_i = 0; weight_i < blend_weights.size(); weight_i++) { + blend_weights.write[weight_i] = 0.0f; + } + + if (d.has("weights")) { + const Array &weights = d["weights"]; + for (int j = 0; j < weights.size(); j++) { + if (j >= blend_weights.size()) { + break; + } + blend_weights.write[j] = weights[j]; + } + mesh->set_blend_weights(blend_weights); + } + mesh->set_mesh(import_mesh); + + state->meshes.push_back(mesh); + } + + print_verbose("glTF: Total meshes: " + itos(state->meshes.size())); + + return OK; +} + +Error GLTFDocument::_serialize_images(Ref state, const String &p_path) { + Array images; + for (int i = 0; i < state->images.size(); i++) { + Dictionary d; + + ERR_CONTINUE(state->images[i].is_null()); + + Ref image = state->images[i]->get_data(); + ERR_CONTINUE(image.is_null()); + + if (p_path.to_lower().ends_with("glb")) { + GLTFBufferViewIndex bvi; + + Ref bv; + bv.instance(); + + const GLTFBufferIndex bi = 0; + bv->buffer = bi; + bv->byte_offset = state->buffers[bi].size(); + ERR_FAIL_INDEX_V(bi, state->buffers.size(), ERR_PARAMETER_RANGE_ERROR); + + PoolVector buffer; + Ref img_tex = image; + if (img_tex.is_valid()) { + image = img_tex->get_data(); + } + Error err = PNGDriverCommon::image_to_png(image, buffer); + ERR_FAIL_COND_V_MSG(err, err, "Can't convert image to PNG."); + + bv->byte_length = buffer.size(); + state->buffers.write[bi].resize(state->buffers[bi].size() + bv->byte_length); + memcpy(&state->buffers.write[bi].write[bv->byte_offset], buffer.read().ptr(), buffer.size()); + ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > state->buffers[bi].size(), ERR_FILE_CORRUPT); + + state->buffer_views.push_back(bv); + bvi = state->buffer_views.size() - 1; + d["bufferView"] = bvi; + d["mimeType"] = "image/png"; + } else { + String name = state->images[i]->get_name(); + if (name.empty()) { + name = itos(i); + } + name = _gen_unique_name(state, name); + name = name.pad_zeros(3); + Ref<_Directory> dir; + dir.instance(); + String texture_dir = "textures"; + String new_texture_dir = p_path.get_base_dir() + "/" + texture_dir; + dir->open(p_path.get_base_dir()); + if (!dir->dir_exists(new_texture_dir)) { + dir->make_dir(new_texture_dir); + } + name = name + ".png"; + image->save_png(new_texture_dir.plus_file(name)); + d["uri"] = texture_dir.plus_file(name); + } + images.push_back(d); + } + + print_verbose("Total images: " + itos(state->images.size())); + + if (!images.size()) { + return OK; + } + state->json["images"] = images; + + return OK; +} + +Error GLTFDocument::_parse_images(Ref state, const String &p_base_path) { + if (!state->json.has("images")) { + return OK; + } + + // Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#images + + const Array &images = state->json["images"]; + for (int i = 0; i < images.size(); i++) { + const Dictionary &d = images[i]; + + // glTF 2.0 supports PNG and JPEG types, which can be specified as (from spec): + // "- a URI to an external file in one of the supported images formats, or + // - a URI with embedded base64-encoded data, or + // - a reference to a bufferView; in that case mimeType must be defined." + // Since mimeType is optional for external files and base64 data, we'll have to + // fall back on letting Godot parse the data to figure out if it's PNG or JPEG. + + // We'll assume that we use either URI or bufferView, so let's warn the user + // if their image somehow uses both. And fail if it has neither. + ERR_CONTINUE_MSG(!d.has("uri") && !d.has("bufferView"), "Invalid image definition in glTF file, it should specific an 'uri' or 'bufferView'."); + if (d.has("uri") && d.has("bufferView")) { + WARN_PRINT("Invalid image definition in glTF file using both 'uri' and 'bufferView'. 'bufferView' will take precedence."); + } + + String mimetype; + if (d.has("mimeType")) { // Should be "image/png" or "image/jpeg". + mimetype = d["mimeType"]; + } + + Vector data; + const uint8_t *data_ptr = nullptr; + int data_size = 0; + + if (d.has("uri")) { + // Handles the first two bullet points from the spec (embedded data, or external file). + String uri = d["uri"]; + + if (uri.begins_with("data:")) { // Embedded data using base64. + // Validate data MIME types and throw a warning if it's one we don't know/support. + if (!uri.begins_with("data:application/octet-stream;base64") && + !uri.begins_with("data:application/gltf-buffer;base64") && + !uri.begins_with("data:image/png;base64") && + !uri.begins_with("data:image/jpeg;base64")) { + WARN_PRINT(vformat("glTF: Image index '%d' uses an unsupported URI data type: %s. Skipping it.", i, uri)); + state->images.push_back(Ref()); // Placeholder to keep count. + continue; + } + data = _parse_base64_uri(uri); + data_ptr = data.ptr(); + data_size = data.size(); + // mimeType is optional, but if we have it defined in the URI, let's use it. + if (mimetype.empty()) { + if (uri.begins_with("data:image/png;base64")) { + mimetype = "image/png"; + } else if (uri.begins_with("data:image/jpeg;base64")) { + mimetype = "image/jpeg"; + } + } + } else { // Relative path to an external image file. + uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. + // ResourceLoader will rely on the file extension to use the relevant loader. + // The spec says that if mimeType is defined, it should take precedence (e.g. + // there could be a `.png` image which is actually JPEG), but there's no easy + // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in + // the material), so we do this only as fallback. + Ref texture = ResourceLoader::load(uri); + if (texture.is_valid()) { + state->images.push_back(texture); + continue; + } else if (mimetype == "image/png" || mimetype == "image/jpeg") { + // Fallback to loading as byte array. + // This enables us to support the spec's requirement that we honor mimetype + // regardless of file URI. + data = FileAccess::get_file_as_array(uri); + if (data.size() == 0) { + WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded as a buffer of MIME type '%s' from URI: %s. Skipping it.", i, mimetype, uri)); + state->images.push_back(Ref()); // Placeholder to keep count. + continue; + } + data_ptr = data.ptr(); + data_size = data.size(); + } else { + WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded from URI: %s. Skipping it.", i, uri)); + state->images.push_back(Ref()); // Placeholder to keep count. + continue; + } + } + } else if (d.has("bufferView")) { + // Handles the third bullet point from the spec (bufferView). + ERR_FAIL_COND_V_MSG(mimetype.empty(), ERR_FILE_CORRUPT, + vformat("glTF: Image index '%d' specifies 'bufferView' but no 'mimeType', which is invalid.", i)); + + const GLTFBufferViewIndex bvi = d["bufferView"]; + + ERR_FAIL_INDEX_V(bvi, state->buffer_views.size(), ERR_PARAMETER_RANGE_ERROR); + + Ref bv = state->buffer_views[bvi]; + + const GLTFBufferIndex bi = bv->buffer; + ERR_FAIL_INDEX_V(bi, state->buffers.size(), ERR_PARAMETER_RANGE_ERROR); + + ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > state->buffers[bi].size(), ERR_FILE_CORRUPT); + + data_ptr = &state->buffers[bi][bv->byte_offset]; + data_size = bv->byte_length; + } + + Ref img; + + if (mimetype == "image/png") { // Load buffer as PNG. + ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE); + img = Image::_png_mem_loader_func(data_ptr, data_size); + } else if (mimetype == "image/jpeg") { // Loader buffer as JPEG. + ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE); + img = Image::_jpg_mem_loader_func(data_ptr, data_size); + } else { + // We can land here if we got an URI with base64-encoded data with application/* MIME type, + // and the optional mimeType property was not defined to tell us how to handle this data (or was invalid). + // So let's try PNG first, then JPEG. + ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE); + img = Image::_png_mem_loader_func(data_ptr, data_size); + if (img.is_null()) { + ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE); + img = Image::_jpg_mem_loader_func(data_ptr, data_size); + } + } + + if (img.is_null()) { + ERR_PRINT(vformat("glTF: Couldn't load image index '%d' with its given mimetype: %s.", i, mimetype)); + state->images.push_back(Ref()); + continue; + } + + Ref t; + t.instance(); + t->create_from_image(img); + + state->images.push_back(t); + } + + print_verbose("glTF: Total images: " + itos(state->images.size())); + + return OK; +} + +Error GLTFDocument::_serialize_textures(Ref state) { + if (!state->textures.size()) { + return OK; + } + + Array textures; + for (int32_t i = 0; i < state->textures.size(); i++) { + Dictionary d; + Ref t = state->textures[i]; + ERR_CONTINUE(t->get_src_image() == -1); + d["source"] = t->get_src_image(); + textures.push_back(d); + } + state->json["textures"] = textures; + + return OK; +} + +Error GLTFDocument::_parse_textures(Ref state) { + if (!state->json.has("textures")) { + return OK; + } + + const Array &textures = state->json["textures"]; + for (GLTFTextureIndex i = 0; i < textures.size(); i++) { + const Dictionary &d = textures[i]; + + ERR_FAIL_COND_V(!d.has("source"), ERR_PARSE_ERROR); + + Ref t; + t.instance(); + t->set_src_image(d["source"]); + state->textures.push_back(t); + } + + return OK; +} + +GLTFTextureIndex GLTFDocument::_set_texture(Ref state, Ref p_texture) { + ERR_FAIL_COND_V(p_texture.is_null(), -1); + Ref gltf_texture; + gltf_texture.instance(); + ERR_FAIL_COND_V(p_texture->get_data().is_null(), -1); + GLTFImageIndex gltf_src_image_i = state->images.size(); + state->images.push_back(p_texture); + gltf_texture->set_src_image(gltf_src_image_i); + GLTFTextureIndex gltf_texture_i = state->textures.size(); + state->textures.push_back(gltf_texture); + return gltf_texture_i; +} + +Ref GLTFDocument::_get_texture(Ref state, const GLTFTextureIndex p_texture) { + ERR_FAIL_INDEX_V(p_texture, state->textures.size(), Ref()); + const GLTFImageIndex image = state->textures[p_texture]->get_src_image(); + + ERR_FAIL_INDEX_V(image, state->images.size(), Ref()); + + return state->images[image]; +} + +Error GLTFDocument::_serialize_materials(Ref state) { + Array materials; + for (int32_t i = 0; i < state->materials.size(); i++) { + Dictionary d; + + Ref material = state->materials[i]; + if (material.is_null()) { + materials.push_back(d); + continue; + } + if (!material->get_name().empty()) { + d["name"] = _gen_unique_name(state, material->get_name()); + } + { + Dictionary mr; + { + Array arr; + const Color c = material->get_albedo().to_linear(); + arr.push_back(c.r); + arr.push_back(c.g); + arr.push_back(c.b); + arr.push_back(c.a); + mr["baseColorFactor"] = arr; + } + { + Dictionary bct; + Ref albedo_texture = material->get_texture(SpatialMaterial::TEXTURE_ALBEDO); + GLTFTextureIndex gltf_texture_index = -1; + + if (albedo_texture.is_valid() && albedo_texture->get_data().is_valid()) { + albedo_texture->set_name(material->get_name() + "_albedo"); + gltf_texture_index = _set_texture(state, albedo_texture); + } + if (gltf_texture_index != -1) { + bct["index"] = gltf_texture_index; + bct["extensions"] = _serialize_texture_transform_uv1(material); + mr["baseColorTexture"] = bct; + } + } + + mr["metallicFactor"] = material->get_metallic(); + mr["roughnessFactor"] = material->get_roughness(); + bool has_roughness = material->get_texture(SpatialMaterial::TEXTURE_ROUGHNESS).is_valid() && material->get_texture(SpatialMaterial::TEXTURE_ROUGHNESS)->get_data().is_valid(); + bool has_ao = material->get_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION) && material->get_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION).is_valid(); + bool has_metalness = material->get_texture(SpatialMaterial::TEXTURE_METALLIC).is_valid() && material->get_texture(SpatialMaterial::TEXTURE_METALLIC)->get_data().is_valid(); + if (has_ao || has_roughness || has_metalness) { + Dictionary mrt; + Ref roughness_texture = material->get_texture(SpatialMaterial::TEXTURE_ROUGHNESS); + SpatialMaterial::TextureChannel roughness_channel = material->get_roughness_texture_channel(); + Ref metallic_texture = material->get_texture(SpatialMaterial::TEXTURE_METALLIC); + SpatialMaterial::TextureChannel metalness_channel = material->get_metallic_texture_channel(); + Ref ao_texture = material->get_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION); + SpatialMaterial::TextureChannel ao_channel = material->get_ao_texture_channel(); + Ref orm_texture; + orm_texture.instance(); + Ref orm_image; + orm_image.instance(); + int32_t height = 0; + int32_t width = 0; + Ref ao_image; + if (has_ao) { + height = ao_texture->get_height(); + width = ao_texture->get_width(); + ao_image = ao_texture->get_data(); + Ref img_tex = ao_image; + if (img_tex.is_valid()) { + ao_image = img_tex->get_data(); + } + if (ao_image->is_compressed()) { + ao_image->decompress(); + } + } + Ref roughness_image; + if (has_roughness) { + height = roughness_texture->get_height(); + width = roughness_texture->get_width(); + roughness_image = roughness_texture->get_data(); + Ref img_tex = roughness_image; + if (img_tex.is_valid()) { + roughness_image = img_tex->get_data(); + } + if (roughness_image->is_compressed()) { + roughness_image->decompress(); + } + } + Ref metallness_image; + if (has_metalness) { + height = metallic_texture->get_height(); + width = metallic_texture->get_width(); + metallness_image = metallic_texture->get_data(); + Ref img_tex = metallness_image; + if (img_tex.is_valid()) { + metallness_image = img_tex->get_data(); + } + if (metallness_image->is_compressed()) { + metallness_image->decompress(); + } + } + Ref albedo_texture = material->get_texture(SpatialMaterial::TEXTURE_ALBEDO); + if (albedo_texture.is_valid() && albedo_texture->get_data().is_valid()) { + height = albedo_texture->get_height(); + width = albedo_texture->get_width(); + } + orm_image->create(width, height, false, Image::FORMAT_RGBA8); + if (ao_image.is_valid() && ao_image->get_size() != Vector2(width, height)) { + ao_image->resize(width, height, Image::INTERPOLATE_LANCZOS); + } + if (roughness_image.is_valid() && roughness_image->get_size() != Vector2(width, height)) { + roughness_image->resize(width, height, Image::INTERPOLATE_LANCZOS); + } + if (metallness_image.is_valid() && metallness_image->get_size() != Vector2(width, height)) { + metallness_image->resize(width, height, Image::INTERPOLATE_LANCZOS); + } + orm_image->lock(); + for (int32_t h = 0; h < height; h++) { + for (int32_t w = 0; w < width; w++) { + Color c = Color(1.0f, 1.0f, 1.0f); + if (has_ao) { + ao_image->lock(); + if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) { + c.r = ao_image->get_pixel(w, h).r; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) { + c.r = ao_image->get_pixel(w, h).g; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) { + c.r = ao_image->get_pixel(w, h).b; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) { + c.r = ao_image->get_pixel(w, h).a; + } + ao_image->lock(); + } + if (has_roughness) { + roughness_image->lock(); + if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).r; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).g; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).b; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).a; + } + roughness_image->unlock(); + } + if (has_metalness) { + metallness_image->lock(); + if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).r; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).g; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).b; + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).a; + } + metallness_image->unlock(); + } + orm_image->set_pixel(w, h, c); + } + } + orm_image->unlock(); + orm_image->generate_mipmaps(); + orm_texture->create_from_image(orm_image); + GLTFTextureIndex orm_texture_index = -1; + if (has_ao || has_roughness || has_metalness) { + orm_texture->set_name(material->get_name() + "_orm"); + orm_texture_index = _set_texture(state, orm_texture); + } + if (has_ao) { + Dictionary ot; + ot["index"] = orm_texture_index; + d["occlusionTexture"] = ot; + } + if (has_roughness || has_metalness) { + mrt["index"] = orm_texture_index; + mrt["extensions"] = _serialize_texture_transform_uv1(material); + mr["metallicRoughnessTexture"] = mrt; + } + } + d["pbrMetallicRoughness"] = mr; + } + + if (material->get_feature(SpatialMaterial::FEATURE_NORMAL_MAPPING)) { + Dictionary nt; + Ref tex; + tex.instance(); + { + Ref normal_texture = material->get_texture(SpatialMaterial::TEXTURE_NORMAL); + // Code for uncompressing RG normal maps + Ref img = normal_texture->get_data(); + Ref img_tex = img; + if (img_tex.is_valid()) { + img = img_tex->get_data(); + } + img->decompress(); + img->convert(Image::FORMAT_RGBA8); + img->lock(); + for (int32_t y = 0; y < img->get_height(); y++) { + for (int32_t x = 0; x < img->get_width(); x++) { + Color c = img->get_pixel(x, y); + Vector2 red_green = Vector2(c.r, c.g); + red_green = red_green * Vector2(2.0f, 2.0f) - Vector2(1.0f, 1.0f); + float blue = 1.0f - red_green.dot(red_green); + blue = MAX(0.0f, blue); + c.b = Math::sqrt(blue); + img->set_pixel(x, y, c); + } + } + img->unlock(); + tex->create_from_image(img); + } + Ref normal_texture = material->get_texture(SpatialMaterial::TEXTURE_NORMAL); + GLTFTextureIndex gltf_texture_index = -1; + if (tex.is_valid() && tex->get_data().is_valid()) { + tex->set_name(material->get_name() + "_normal"); + gltf_texture_index = _set_texture(state, tex); + } + nt["scale"] = material->get_normal_scale(); + if (gltf_texture_index != -1) { + nt["index"] = gltf_texture_index; + d["normalTexture"] = nt; + } + } + + if (material->get_feature(SpatialMaterial::FEATURE_EMISSION)) { + const Color c = material->get_emission().to_srgb(); + Array arr; + arr.push_back(c.r); + arr.push_back(c.g); + arr.push_back(c.b); + d["emissiveFactor"] = arr; + } + if (material->get_feature(SpatialMaterial::FEATURE_EMISSION)) { + Dictionary et; + Ref emission_texture = material->get_texture(SpatialMaterial::TEXTURE_EMISSION); + GLTFTextureIndex gltf_texture_index = -1; + if (emission_texture.is_valid() && emission_texture->get_data().is_valid()) { + emission_texture->set_name(material->get_name() + "_emission"); + gltf_texture_index = _set_texture(state, emission_texture); + } + + if (gltf_texture_index != -1) { + et["index"] = gltf_texture_index; + d["emissiveTexture"] = et; + } + } + const bool ds = material->get_cull_mode() == SpatialMaterial::CULL_DISABLED; + if (ds) { + d["doubleSided"] = ds; + } + if (material->get_feature(SpatialMaterial::FEATURE_TRANSPARENT)) { + if (material->get_flag(SpatialMaterial::FLAG_USE_ALPHA_SCISSOR)) { + d["alphaMode"] = "MASK"; + d["alphaCutoff"] = material->get_alpha_scissor_threshold(); + } else { + d["alphaMode"] = "BLEND"; + } + } + materials.push_back(d); + } + state->json["materials"] = materials; + print_verbose("Total materials: " + itos(state->materials.size())); + + return OK; +} + +Error GLTFDocument::_parse_materials(Ref state) { + if (!state->json.has("materials")) { + return OK; + } + + const Array &materials = state->json["materials"]; + for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { + const Dictionary &d = materials[i]; + + Ref material; + material.instance(); + if (d.has("name") && !String(d["name"]).empty()) { + material->set_name(d["name"]); + } else { + material->set_name(vformat("material_%s", itos(i))); + } + + material->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + Dictionary pbr_spec_gloss_extensions; + if (d.has("extensions")) { + pbr_spec_gloss_extensions = d["extensions"]; + } + if (pbr_spec_gloss_extensions.has("KHR_materials_pbrSpecularGlossiness")) { + WARN_PRINT("Material uses a specular and glossiness workflow. Textures will be converted to roughness and metallic workflow, which may not be 100% accurate."); + Dictionary sgm = pbr_spec_gloss_extensions["KHR_materials_pbrSpecularGlossiness"]; + + Ref spec_gloss; + spec_gloss.instance(); + if (sgm.has("diffuseTexture")) { + const Dictionary &diffuse_texture_dict = sgm["diffuseTexture"]; + if (diffuse_texture_dict.has("index")) { + Ref diffuse_texture = _get_texture(state, diffuse_texture_dict["index"]); + if (diffuse_texture.is_valid()) { + spec_gloss->diffuse_img = diffuse_texture->get_data(); + material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, diffuse_texture); + } + } + } + if (sgm.has("diffuseFactor")) { + const Array &arr = sgm["diffuseFactor"]; + ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); + spec_gloss->diffuse_factor = c; + material->set_albedo(spec_gloss->diffuse_factor); + } + + if (sgm.has("specularFactor")) { + const Array &arr = sgm["specularFactor"]; + ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); + spec_gloss->specular_factor = Color(arr[0], arr[1], arr[2]); + } + + if (sgm.has("glossinessFactor")) { + spec_gloss->gloss_factor = sgm["glossinessFactor"]; + material->set_roughness(1.0f - CLAMP(spec_gloss->gloss_factor, 0.0f, 1.0f)); + } + if (sgm.has("specularGlossinessTexture")) { + const Dictionary &spec_gloss_texture = sgm["specularGlossinessTexture"]; + if (spec_gloss_texture.has("index")) { + const Ref orig_texture = _get_texture(state, spec_gloss_texture["index"]); + if (orig_texture.is_valid()) { + spec_gloss->spec_gloss_img = orig_texture->get_data(); + } + } + } + spec_gloss_to_rough_metal(spec_gloss, material); + + } else if (d.has("pbrMetallicRoughness")) { + const Dictionary &mr = d["pbrMetallicRoughness"]; + if (mr.has("baseColorFactor")) { + const Array &arr = mr["baseColorFactor"]; + ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); + material->set_albedo(c); + } + + if (mr.has("baseColorTexture")) { + const Dictionary &bct = mr["baseColorTexture"]; + if (bct.has("index")) { + material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, _get_texture(state, bct["index"])); + } + if (!mr.has("baseColorFactor")) { + material->set_albedo(Color(1, 1, 1)); + } + _set_texture_transform_uv1(bct, material); + } + + if (mr.has("metallicFactor")) { + material->set_metallic(mr["metallicFactor"]); + } else { + material->set_metallic(1.0); + } + + if (mr.has("roughnessFactor")) { + material->set_roughness(mr["roughnessFactor"]); + } else { + material->set_roughness(1.0); + } + + if (mr.has("metallicRoughnessTexture")) { + const Dictionary &bct = mr["metallicRoughnessTexture"]; + if (bct.has("index")) { + const Ref t = _get_texture(state, bct["index"]); + material->set_texture(SpatialMaterial::TEXTURE_METALLIC, t); + material->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); + material->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, t); + material->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); + if (!mr.has("metallicFactor")) { + material->set_metallic(1); + } + if (!mr.has("roughnessFactor")) { + material->set_roughness(1); + } + } + } + } + + if (d.has("normalTexture")) { + const Dictionary &bct = d["normalTexture"]; + if (bct.has("index")) { + material->set_texture(SpatialMaterial::TEXTURE_NORMAL, _get_texture(state, bct["index"])); + material->set_feature(SpatialMaterial::FEATURE_NORMAL_MAPPING, true); + } + if (bct.has("scale")) { + material->set_normal_scale(bct["scale"]); + } + } + if (d.has("occlusionTexture")) { + const Dictionary &bct = d["occlusionTexture"]; + if (bct.has("index")) { + material->set_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION, _get_texture(state, bct["index"])); + material->set_ao_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_RED); + material->set_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION, true); + } + } + + if (d.has("emissiveFactor")) { + const Array &arr = d["emissiveFactor"]; + ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); + material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); + + material->set_emission(c); + } + + if (d.has("emissiveTexture")) { + const Dictionary &bct = d["emissiveTexture"]; + if (bct.has("index")) { + material->set_texture(SpatialMaterial::TEXTURE_EMISSION, _get_texture(state, bct["index"])); + material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); + material->set_emission(Color(0, 0, 0)); + } + } + + if (d.has("doubleSided")) { + const bool ds = d["doubleSided"]; + if (ds) { + material->set_cull_mode(SpatialMaterial::CULL_DISABLED); + } + } + + if (d.has("alphaMode")) { + const String &am = d["alphaMode"]; + if (am == "BLEND") { + material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + material->set_depth_draw_mode(SpatialMaterial::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + } else if (am == "MASK") { + material->set_flag(SpatialMaterial::FLAG_USE_ALPHA_SCISSOR, true); + if (d.has("alphaCutoff")) { + material->set_alpha_scissor_threshold(d["alphaCutoff"]); + } else { + material->set_alpha_scissor_threshold(0.5f); + } + } + } + state->materials.push_back(material); + } + + print_verbose("Total materials: " + itos(state->materials.size())); + + return OK; +} + +void GLTFDocument::_set_texture_transform_uv1(const Dictionary &d, Ref material) { + if (d.has("extensions")) { + const Dictionary &extensions = d["extensions"]; + if (extensions.has("KHR_texture_transform")) { + const Dictionary &texture_transform = extensions["KHR_texture_transform"]; + const Array &offset_arr = texture_transform["offset"]; + if (offset_arr.size() == 2) { + const Vector3 offset_vector3 = Vector3(offset_arr[0], offset_arr[1], 0.0f); + material->set_uv1_offset(offset_vector3); + } + + const Array &scale_arr = texture_transform["scale"]; + if (scale_arr.size() == 2) { + const Vector3 scale_vector3 = Vector3(scale_arr[0], scale_arr[1], 1.0f); + material->set_uv1_scale(scale_vector3); + } + } + } +} + +void GLTFDocument::spec_gloss_to_rough_metal(Ref r_spec_gloss, Ref p_material) { + if (r_spec_gloss->spec_gloss_img.is_null()) { + return; + } + if (r_spec_gloss->diffuse_img.is_null()) { + return; + } + Ref rm_img; + rm_img.instance(); + bool has_roughness = false; + bool has_metal = false; + p_material->set_roughness(1.0f); + p_material->set_metallic(1.0f); + rm_img->create(r_spec_gloss->spec_gloss_img->get_width(), r_spec_gloss->spec_gloss_img->get_height(), false, Image::FORMAT_RGBA8); + rm_img->lock(); + r_spec_gloss->spec_gloss_img->decompress(); + if (r_spec_gloss->diffuse_img.is_valid()) { + r_spec_gloss->diffuse_img->decompress(); + r_spec_gloss->diffuse_img->resize(r_spec_gloss->spec_gloss_img->get_width(), r_spec_gloss->spec_gloss_img->get_height(), Image::INTERPOLATE_LANCZOS); + r_spec_gloss->spec_gloss_img->resize(r_spec_gloss->diffuse_img->get_width(), r_spec_gloss->diffuse_img->get_height(), Image::INTERPOLATE_LANCZOS); + } + for (int32_t y = 0; y < r_spec_gloss->spec_gloss_img->get_height(); y++) { + for (int32_t x = 0; x < r_spec_gloss->spec_gloss_img->get_width(); x++) { + const Color specular_pixel = r_spec_gloss->spec_gloss_img->get_pixel(x, y).to_linear(); + Color specular = Color(specular_pixel.r, specular_pixel.g, specular_pixel.b); + specular *= r_spec_gloss->specular_factor; + Color diffuse = Color(1.0f, 1.0f, 1.0f); + r_spec_gloss->diffuse_img->lock(); + diffuse *= r_spec_gloss->diffuse_img->get_pixel(x, y).to_linear(); + float metallic = 0.0f; + Color base_color; + spec_gloss_to_metal_base_color(specular, diffuse, base_color, metallic); + Color mr = Color(1.0f, 1.0f, 1.0f); + mr.g = specular_pixel.a; + mr.b = metallic; + if (!Math::is_equal_approx(mr.g, 1.0f)) { + has_roughness = true; + } + if (!Math::is_equal_approx(mr.b, 0.0f)) { + has_metal = true; + } + mr.g *= r_spec_gloss->gloss_factor; + mr.g = 1.0f - mr.g; + rm_img->set_pixel(x, y, mr); + r_spec_gloss->diffuse_img->set_pixel(x, y, base_color.to_srgb()); + r_spec_gloss->diffuse_img->unlock(); + } + } + rm_img->unlock(); + rm_img->generate_mipmaps(); + r_spec_gloss->diffuse_img->generate_mipmaps(); + Ref diffuse_image_texture; + diffuse_image_texture.instance(); + diffuse_image_texture->create_from_image(r_spec_gloss->diffuse_img); + p_material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, diffuse_image_texture); + Ref rm_image_texture; + rm_image_texture.instance(); + rm_image_texture->create_from_image(rm_img); + if (has_roughness) { + p_material->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, rm_image_texture); + p_material->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); + } + + if (has_metal) { + p_material->set_texture(SpatialMaterial::TEXTURE_METALLIC, rm_image_texture); + p_material->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); + } +} + +void GLTFDocument::spec_gloss_to_metal_base_color(const Color &p_specular_factor, const Color &p_diffuse, Color &r_base_color, float &r_metallic) { + const Color DIELECTRIC_SPECULAR = Color(0.04f, 0.04f, 0.04f); + Color specular = Color(p_specular_factor.r, p_specular_factor.g, p_specular_factor.b); + const float one_minus_specular_strength = 1.0f - get_max_component(specular); + const float dielectric_specular_red = DIELECTRIC_SPECULAR.r; + float brightness_diffuse = get_perceived_brightness(p_diffuse); + const float brightness_specular = get_perceived_brightness(specular); + r_metallic = solve_metallic(dielectric_specular_red, brightness_diffuse, brightness_specular, one_minus_specular_strength); + const float one_minus_metallic = 1.0f - r_metallic; + const Color base_color_from_diffuse = p_diffuse * (one_minus_specular_strength / (1.0f - dielectric_specular_red) / MAX(one_minus_metallic, CMP_EPSILON)); + const Color base_color_from_specular = (specular - (DIELECTRIC_SPECULAR * (one_minus_metallic))) * (1.0f / MAX(r_metallic, CMP_EPSILON)); + r_base_color.r = Math::lerp(base_color_from_diffuse.r, base_color_from_specular.r, r_metallic * r_metallic); + r_base_color.g = Math::lerp(base_color_from_diffuse.g, base_color_from_specular.g, r_metallic * r_metallic); + r_base_color.b = Math::lerp(base_color_from_diffuse.b, base_color_from_specular.b, r_metallic * r_metallic); + r_base_color.a = p_diffuse.a; + r_base_color.r = CLAMP(r_base_color.r, 0.0f, 1.0f); + r_base_color.g = CLAMP(r_base_color.g, 0.0f, 1.0f); + r_base_color.b = CLAMP(r_base_color.b, 0.0f, 1.0f); + r_base_color.a = CLAMP(r_base_color.a, 0.0f, 1.0f); +} + +GLTFNodeIndex GLTFDocument::_find_highest_node(Ref state, const Vector &subset) { + int highest = -1; + GLTFNodeIndex best_node = -1; + + for (int i = 0; i < subset.size(); ++i) { + const GLTFNodeIndex node_i = subset[i]; + const Ref node = state->nodes[node_i]; + + if (highest == -1 || node->height < highest) { + highest = node->height; + best_node = node_i; + } + } + + return best_node; +} + +bool GLTFDocument::_capture_nodes_in_skin(Ref state, Ref skin, const GLTFNodeIndex node_index) { + bool found_joint = false; + + for (int i = 0; i < state->nodes[node_index]->children.size(); ++i) { + found_joint |= _capture_nodes_in_skin(state, skin, state->nodes[node_index]->children[i]); + } + + if (found_joint) { + // Mark it if we happen to find another skins joint... + if (state->nodes[node_index]->joint && skin->joints.find(node_index) < 0) { + skin->joints.push_back(node_index); + } else if (skin->non_joints.find(node_index) < 0) { + skin->non_joints.push_back(node_index); + } + } + + if (skin->joints.find(node_index) > 0) { + return true; + } + + return false; +} + +void GLTFDocument::_capture_nodes_for_multirooted_skin(Ref state, Ref skin) { + DisjointSet disjoint_set; + + for (int i = 0; i < skin->joints.size(); ++i) { + const GLTFNodeIndex node_index = skin->joints[i]; + const GLTFNodeIndex parent = state->nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (skin->joints.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector roots; + disjoint_set.get_representatives(roots); + + if (roots.size() <= 1) { + return; + } + + int maxHeight = -1; + + // Determine the max height rooted tree + for (int i = 0; i < roots.size(); ++i) { + const GLTFNodeIndex root = roots[i]; + + if (maxHeight == -1 || state->nodes[root]->height < maxHeight) { + maxHeight = state->nodes[root]->height; + } + } + + // Go up the tree till all of the multiple roots of the skin are at the same hierarchy level. + // This sucks, but 99% of all game engines (not just Godot) would have this same issue. + for (int i = 0; i < roots.size(); ++i) { + GLTFNodeIndex current_node = roots[i]; + while (state->nodes[current_node]->height > maxHeight) { + GLTFNodeIndex parent = state->nodes[current_node]->parent; + + if (state->nodes[parent]->joint && skin->joints.find(parent) < 0) { + skin->joints.push_back(parent); + } else if (skin->non_joints.find(parent) < 0) { + skin->non_joints.push_back(parent); + } + + current_node = parent; + } + + // replace the roots + roots.write[i] = current_node; + } + + // Climb up the tree until they all have the same parent + bool all_same; + + do { + all_same = true; + const GLTFNodeIndex first_parent = state->nodes[roots[0]]->parent; + + for (int i = 1; i < roots.size(); ++i) { + all_same &= (first_parent == state->nodes[roots[i]]->parent); + } + + if (!all_same) { + for (int i = 0; i < roots.size(); ++i) { + const GLTFNodeIndex current_node = roots[i]; + const GLTFNodeIndex parent = state->nodes[current_node]->parent; + + if (state->nodes[parent]->joint && skin->joints.find(parent) < 0) { + skin->joints.push_back(parent); + } else if (skin->non_joints.find(parent) < 0) { + skin->non_joints.push_back(parent); + } + + roots.write[i] = parent; + } + } + + } while (!all_same); +} + +Error GLTFDocument::_expand_skin(Ref state, Ref skin) { + _capture_nodes_for_multirooted_skin(state, skin); + + // Grab all nodes that lay in between skin joints/nodes + DisjointSet disjoint_set; + + Vector all_skin_nodes; + all_skin_nodes.append_array(skin->joints); + all_skin_nodes.append_array(skin->non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state->nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector out_owners; + disjoint_set.get_representatives(out_owners); + + Vector out_roots; + + for (int i = 0; i < out_owners.size(); ++i) { + Vector set; + disjoint_set.get_members(set, out_owners[i]); + + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + out_roots.push_back(root); + } + + out_roots.sort(); + + for (int i = 0; i < out_roots.size(); ++i) { + _capture_nodes_in_skin(state, skin, out_roots[i]); + } + + skin->roots = out_roots; + + return OK; +} + +Error GLTFDocument::_verify_skin(Ref state, Ref skin) { + // This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is) + // In case additional interpolating logic is added to the skins, this will help ensure that you + // do not cause it to self implode into a fiery blaze + + // We are going to re-calculate the root nodes and compare them to the ones saved in the skin, + // then ensure the multiple trees (if they exist) are on the same sublevel + + // Grab all nodes that lay in between skin joints/nodes + DisjointSet disjoint_set; + + Vector all_skin_nodes; + all_skin_nodes.append_array(skin->joints); + all_skin_nodes.append_array(skin->non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state->nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector out_owners; + disjoint_set.get_representatives(out_owners); + + Vector out_roots; + + for (int i = 0; i < out_owners.size(); ++i) { + Vector set; + disjoint_set.get_members(set, out_owners[i]); + + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + out_roots.push_back(root); + } + + out_roots.sort(); + + ERR_FAIL_COND_V(out_roots.size() == 0, FAILED); + + // Make sure the roots are the exact same (they better be) + ERR_FAIL_COND_V(out_roots.size() != skin->roots.size(), FAILED); + for (int i = 0; i < out_roots.size(); ++i) { + ERR_FAIL_COND_V(out_roots[i] != skin->roots[i], FAILED); + } + + // Single rooted skin? Perfectly ok! + if (out_roots.size() == 1) { + return OK; + } + + // Make sure all parents of a multi-rooted skin are the SAME + const GLTFNodeIndex parent = state->nodes[out_roots[0]]->parent; + for (int i = 1; i < out_roots.size(); ++i) { + if (state->nodes[out_roots[i]]->parent != parent) { + return FAILED; + } + } + + return OK; +} + +Error GLTFDocument::_parse_skins(Ref state) { + if (!state->json.has("skins")) { + return OK; + } + + const Array &skins = state->json["skins"]; + + // Create the base skins, and mark nodes that are joints + for (int i = 0; i < skins.size(); i++) { + const Dictionary &d = skins[i]; + + Ref skin; + skin.instance(); + + ERR_FAIL_COND_V(!d.has("joints"), ERR_PARSE_ERROR); + + const Array &joints = d["joints"]; + + if (d.has("inverseBindMatrices")) { + skin->inverse_binds = _decode_accessor_as_xform(state, d["inverseBindMatrices"], false); + ERR_FAIL_COND_V(skin->inverse_binds.size() != joints.size(), ERR_PARSE_ERROR); + } + + for (int j = 0; j < joints.size(); j++) { + const GLTFNodeIndex node = joints[j]; + ERR_FAIL_INDEX_V(node, state->nodes.size(), ERR_PARSE_ERROR); + + skin->joints.push_back(node); + skin->joints_original.push_back(node); + + state->nodes.write[node]->joint = true; + } + + if (d.has("name") && !String(d["name"]).empty()) { + skin->set_name(d["name"]); + } else { + skin->set_name(vformat("skin_%s", itos(i))); + } + + if (d.has("skeleton")) { + skin->skin_root = d["skeleton"]; + } + + state->skins.push_back(skin); + } + + for (GLTFSkinIndex i = 0; i < state->skins.size(); ++i) { + Ref skin = state->skins.write[i]; + + // Expand the skin to capture all the extra non-joints that lie in between the actual joints, + // and expand the hierarchy to ensure multi-rooted trees lie on the same height level + ERR_FAIL_COND_V(_expand_skin(state, skin), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(_verify_skin(state, skin), ERR_PARSE_ERROR); + } + + print_verbose("glTF: Total skins: " + itos(state->skins.size())); + + return OK; +} + +Error GLTFDocument::_determine_skeletons(Ref state) { + // Using a disjoint set, we are going to potentially combine all skins that are actually branches + // of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton. + // This is another unclear issue caused by the current glTF specification. + + DisjointSet skeleton_sets; + + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + const Ref skin = state->skins[skin_i]; + + Vector all_skin_nodes; + all_skin_nodes.append_array(skin->joints); + all_skin_nodes.append_array(skin->non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state->nodes[node_index]->parent; + skeleton_sets.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + skeleton_sets.create_union(parent, node_index); + } + } + + // We are going to connect the separate skin subtrees in each skin together + // so that the final roots are entire sets of valid skin trees + for (int i = 1; i < skin->roots.size(); ++i) { + skeleton_sets.create_union(skin->roots[0], skin->roots[i]); + } + } + + { // attempt to joint all touching subsets (siblings/parent are part of another skin) + Vector groups_representatives; + skeleton_sets.get_representatives(groups_representatives); + + Vector highest_group_members; + Vector> groups; + for (int i = 0; i < groups_representatives.size(); ++i) { + Vector group; + skeleton_sets.get_members(group, groups_representatives[i]); + highest_group_members.push_back(_find_highest_node(state, group)); + groups.push_back(group); + } + + for (int i = 0; i < highest_group_members.size(); ++i) { + const GLTFNodeIndex node_i = highest_group_members[i]; + + // Attach any siblings together (this needs to be done n^2/2 times) + for (int j = i + 1; j < highest_group_members.size(); ++j) { + const GLTFNodeIndex node_j = highest_group_members[j]; + + // Even if they are siblings under the root! :) + if (state->nodes[node_i]->parent == state->nodes[node_j]->parent) { + skeleton_sets.create_union(node_i, node_j); + } + } + + // Attach any parenting going on together (we need to do this n^2 times) + const GLTFNodeIndex node_i_parent = state->nodes[node_i]->parent; + if (node_i_parent >= 0) { + for (int j = 0; j < groups.size() && i != j; ++j) { + const Vector &group = groups[j]; + + if (group.find(node_i_parent) >= 0) { + const GLTFNodeIndex node_j = highest_group_members[j]; + skeleton_sets.create_union(node_i, node_j); + } + } + } + } + } + + // At this point, the skeleton groups should be finalized + Vector skeleton_owners; + skeleton_sets.get_representatives(skeleton_owners); + + // Mark all the skins actual skeletons, after we have merged them + for (GLTFSkeletonIndex skel_i = 0; skel_i < skeleton_owners.size(); ++skel_i) { + const GLTFNodeIndex skeleton_owner = skeleton_owners[skel_i]; + Ref skeleton; + skeleton.instance(); + + Vector skeleton_nodes; + skeleton_sets.get_members(skeleton_nodes, skeleton_owner); + + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref skin = state->skins.write[skin_i]; + + // If any of the the skeletons nodes exist in a skin, that skin now maps to the skeleton + for (int i = 0; i < skeleton_nodes.size(); ++i) { + GLTFNodeIndex skel_node_i = skeleton_nodes[i]; + if (skin->joints.find(skel_node_i) >= 0 || skin->non_joints.find(skel_node_i) >= 0) { + skin->skeleton = skel_i; + continue; + } + } + } + + Vector non_joints; + for (int i = 0; i < skeleton_nodes.size(); ++i) { + const GLTFNodeIndex node_i = skeleton_nodes[i]; + + if (state->nodes[node_i]->joint) { + skeleton->joints.push_back(node_i); + } else { + non_joints.push_back(node_i); + } + } + + state->skeletons.push_back(skeleton); + + _reparent_non_joint_skeleton_subtrees(state, state->skeletons.write[skel_i], non_joints); + } + + for (GLTFSkeletonIndex skel_i = 0; skel_i < state->skeletons.size(); ++skel_i) { + Ref skeleton = state->skeletons.write[skel_i]; + + for (int i = 0; i < skeleton->joints.size(); ++i) { + const GLTFNodeIndex node_i = skeleton->joints[i]; + Ref node = state->nodes[node_i]; + + ERR_FAIL_COND_V(!node->joint, ERR_PARSE_ERROR); + ERR_FAIL_COND_V(node->skeleton >= 0, ERR_PARSE_ERROR); + node->skeleton = skel_i; + } + + ERR_FAIL_COND_V(_determine_skeleton_roots(state, skel_i), ERR_PARSE_ERROR); + } + + return OK; +} + +Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref state, Ref skeleton, const Vector &non_joints) { + DisjointSet subtree_set; + + // Populate the disjoint set with ONLY non joints that are in the skeleton hierarchy (non_joints vector) + // This way we can find any joints that lie in between joints, as the current glTF specification + // mentions nothing about non-joints being in between joints of the same skin. Hopefully one day we + // can remove this code. + + // skinD depicted here explains this issue: + // https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Animation_Skin + + for (int i = 0; i < non_joints.size(); ++i) { + const GLTFNodeIndex node_i = non_joints[i]; + + subtree_set.insert(node_i); + + const GLTFNodeIndex parent_i = state->nodes[node_i]->parent; + if (parent_i >= 0 && non_joints.find(parent_i) >= 0 && !state->nodes[parent_i]->joint) { + subtree_set.create_union(parent_i, node_i); + } + } + + // Find all the non joint subtrees and re-parent them to a new "fake" joint + + Vector non_joint_subtree_roots; + subtree_set.get_representatives(non_joint_subtree_roots); + + for (int root_i = 0; root_i < non_joint_subtree_roots.size(); ++root_i) { + const GLTFNodeIndex subtree_root = non_joint_subtree_roots[root_i]; + + Vector subtree_nodes; + subtree_set.get_members(subtree_nodes, subtree_root); + + for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) { + ERR_FAIL_COND_V(_reparent_to_fake_joint(state, skeleton, subtree_nodes[subtree_i]), FAILED); + + // We modified the tree, recompute all the heights + _compute_node_heights(state); + } + } + + return OK; +} + +Error GLTFDocument::_reparent_to_fake_joint(Ref state, Ref skeleton, const GLTFNodeIndex node_index) { + Ref node = state->nodes[node_index]; + + // Can we just "steal" this joint if it is just a spatial node? + if (node->skin < 0 && node->mesh < 0 && node->camera < 0) { + node->joint = true; + // Add the joint to the skeletons joints + skeleton->joints.push_back(node_index); + return OK; + } + + GLTFNode *fake_joint = memnew(GLTFNode); + const GLTFNodeIndex fake_joint_index = state->nodes.size(); + state->nodes.push_back(fake_joint); + + // We better not be a joint, or we messed up in our logic + if (node->joint) + return FAILED; + + fake_joint->translation = node->translation; + fake_joint->rotation = node->rotation; + fake_joint->scale = node->scale; + fake_joint->xform = node->xform; + fake_joint->joint = true; + + // We can use the exact same name here, because the joint will be inside a skeleton and not the scene + fake_joint->set_name(node->get_name()); + + // Clear the nodes transforms, since it will be parented to the fake joint + node->translation = Vector3(0, 0, 0); + node->rotation = Quat(); + node->scale = Vector3(1, 1, 1); + node->xform = Transform(); + + // Transfer the node children to the fake joint + for (int child_i = 0; child_i < node->children.size(); ++child_i) { + Ref child = state->nodes[node->children[child_i]]; + child->parent = fake_joint_index; + } + + fake_joint->children = node->children; + node->children.clear(); + + // add the fake joint to the parent and remove the original joint + if (node->parent >= 0) { + Ref parent = state->nodes[node->parent]; + parent->children.erase(node_index); + parent->children.push_back(fake_joint_index); + fake_joint->parent = node->parent; + } + + // Add the node to the fake joint + fake_joint->children.push_back(node_index); + node->parent = fake_joint_index; + node->fake_joint_parent = fake_joint_index; + + // Add the fake joint to the skeletons joints + skeleton->joints.push_back(fake_joint_index); + + // Replace skin_skeletons with fake joints if we must. + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref skin = state->skins.write[skin_i]; + if (skin->skin_root == node_index) { + skin->skin_root = fake_joint_index; + } + } + + return OK; +} + +Error GLTFDocument::_determine_skeleton_roots(Ref state, const GLTFSkeletonIndex skel_i) { + DisjointSet disjoint_set; + + for (GLTFNodeIndex i = 0; i < state->nodes.size(); ++i) { + const Ref node = state->nodes[i]; + + if (node->skeleton != skel_i) { + continue; + } + + disjoint_set.insert(i); + + if (node->parent >= 0 && state->nodes[node->parent]->skeleton == skel_i) { + disjoint_set.create_union(node->parent, i); + } + } + + Ref skeleton = state->skeletons.write[skel_i]; + + Vector owners; + disjoint_set.get_representatives(owners); + + Vector roots; + + for (int i = 0; i < owners.size(); ++i) { + Vector set; + disjoint_set.get_members(set, owners[i]); + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + roots.push_back(root); + } + + roots.sort(); + PoolVector roots_array; + roots_array.resize(roots.size()); + PoolVector::Write write_roots = roots_array.write(); + for (int32_t root_i = 0; root_i < roots_array.size(); root_i++) { + write_roots[root_i] = roots[root_i]; + } + skeleton->roots = roots_array; + + if (roots.size() == 0) { + return FAILED; + } else if (roots.size() == 1) { + return OK; + } + + // Check that the subtrees have the same parent root + const GLTFNodeIndex parent = state->nodes[roots[0]]->parent; + for (int i = 1; i < roots.size(); ++i) { + if (state->nodes[roots[i]]->parent != parent) { + return FAILED; + } + } + + return OK; +} + +Error GLTFDocument::_create_skeletons(Ref state) { + for (GLTFSkeletonIndex skel_i = 0; skel_i < state->skeletons.size(); ++skel_i) { + Ref gltf_skeleton = state->skeletons.write[skel_i]; + + Skeleton *skeleton = memnew(Skeleton); + gltf_skeleton->godot_skeleton = skeleton; + + // Make a unique name, no gltf node represents this skeleton + skeleton->set_name(_gen_unique_name(state, "Skeleton")); + + List bones; + + for (int i = 0; i < gltf_skeleton->roots.size(); ++i) { + bones.push_back(gltf_skeleton->roots[i]); + } + + // Make the skeleton creation deterministic by going through the roots in + // a sorted order, and DEPTH FIRST + bones.sort(); + + while (!bones.empty()) { + const GLTFNodeIndex node_i = bones.front()->get(); + bones.pop_front(); + + Ref node = state->nodes[node_i]; + ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED); + + { // Add all child nodes to the stack (deterministically) + Vector child_nodes; + for (int i = 0; i < node->children.size(); ++i) { + const GLTFNodeIndex child_i = node->children[i]; + if (state->nodes[child_i]->skeleton == skel_i) { + child_nodes.push_back(child_i); + } + } + + // Depth first insertion + child_nodes.sort(); + for (int i = child_nodes.size() - 1; i >= 0; --i) { + bones.push_front(child_nodes[i]); + } + } + + const int bone_index = skeleton->get_bone_count(); + + if (node->get_name().empty()) { + node->set_name("bone"); + } + + node->set_name(_gen_unique_bone_name(state, skel_i, node->get_name())); + + skeleton->add_bone(node->get_name()); + skeleton->set_bone_rest(bone_index, node->xform); + + if (node->parent >= 0 && state->nodes[node->parent]->skeleton == skel_i) { + const int bone_parent = skeleton->find_bone(state->nodes[node->parent]->get_name()); + ERR_FAIL_COND_V(bone_parent < 0, FAILED); + skeleton->set_bone_parent(bone_index, skeleton->find_bone(state->nodes[node->parent]->get_name())); + } + + state->scene_nodes.insert(node_i, skeleton); + } + } + + ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(state), ERR_PARSE_ERROR); + + return OK; +} + +Error GLTFDocument::_map_skin_joints_indices_to_skeleton_bone_indices(Ref state) { + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref skin = state->skins.write[skin_i]; + + Ref skeleton = state->skeletons[skin->skeleton]; + + for (int joint_index = 0; joint_index < skin->joints_original.size(); ++joint_index) { + const GLTFNodeIndex node_i = skin->joints_original[joint_index]; + const Ref node = state->nodes[node_i]; + + const int bone_index = skeleton->godot_skeleton->find_bone(node->get_name()); + ERR_FAIL_COND_V(bone_index < 0, FAILED); + + skin->joint_i_to_bone_i.insert(joint_index, bone_index); + } + } + + return OK; +} + +Error GLTFDocument::_serialize_skins(Ref state) { + _remove_duplicate_skins(state); + return OK; +} + +Error GLTFDocument::_create_skins(Ref state) { + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref gltf_skin = state->skins.write[skin_i]; + + Ref skin; + skin.instance(); + + // Some skins don't have IBM's! What absolute monsters! + const bool has_ibms = !gltf_skin->inverse_binds.empty(); + + for (int joint_i = 0; joint_i < gltf_skin->joints_original.size(); ++joint_i) { + GLTFNodeIndex node = gltf_skin->joints_original[joint_i]; + String bone_name = state->nodes[node]->get_name(); + + Transform xform; + if (has_ibms) { + xform = gltf_skin->inverse_binds[joint_i]; + } + + if (state->use_named_skin_binds) { + skin->add_named_bind(bone_name, xform); + } else { + int32_t bone_i = gltf_skin->joint_i_to_bone_i[joint_i]; + skin->add_bind(bone_i, xform); + } + } + + gltf_skin->godot_skin = skin; + } + + // Purge the duplicates! + _remove_duplicate_skins(state); + + // Create unique names now, after removing duplicates + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref skin = state->skins.write[skin_i]->godot_skin; + if (skin->get_name().empty()) { + // Make a unique name, no gltf node represents this skin + skin->set_name(_gen_unique_name(state, "Skin")); + } + } + + return OK; +} + +bool GLTFDocument::_skins_are_same(const Ref skin_a, const Ref skin_b) { + if (skin_a->get_bind_count() != skin_b->get_bind_count()) { + return false; + } + + for (int i = 0; i < skin_a->get_bind_count(); ++i) { + if (skin_a->get_bind_bone(i) != skin_b->get_bind_bone(i)) { + return false; + } + if (skin_a->get_bind_name(i) != skin_b->get_bind_name(i)) { + return false; + } + + Transform a_xform = skin_a->get_bind_pose(i); + Transform b_xform = skin_b->get_bind_pose(i); + + if (a_xform != b_xform) { + return false; + } + } + + return true; +} + +void GLTFDocument::_remove_duplicate_skins(Ref state) { + for (int i = 0; i < state->skins.size(); ++i) { + for (int j = i + 1; j < state->skins.size(); ++j) { + const Ref skin_i = state->skins[i]->godot_skin; + const Ref skin_j = state->skins[j]->godot_skin; + + if (_skins_are_same(skin_i, skin_j)) { + // replace it and delete the old + state->skins.write[j]->godot_skin = skin_i; + } + } + } +} + +Error GLTFDocument::_serialize_lights(Ref state) { + Array lights; + for (GLTFLightIndex i = 0; i < state->lights.size(); i++) { + Dictionary d; + Ref light = state->lights[i]; + Array color; + color.resize(3); + color[0] = light->color.r; + color[1] = light->color.g; + color[2] = light->color.b; + d["color"] = color; + d["type"] = light->type; + if (light->type == "spot") { + Dictionary s; + float inner_cone_angle = light->inner_cone_angle; + s["innerConeAngle"] = inner_cone_angle; + float outer_cone_angle = light->outer_cone_angle; + s["outerConeAngle"] = outer_cone_angle; + d["spot"] = s; + } + float intensity = light->intensity; + d["intensity"] = intensity; + float range = light->range; + d["range"] = range; + lights.push_back(d); + } + + if (!state->lights.size()) { + return OK; + } + + Dictionary extensions; + if (state->json.has("extensions")) { + extensions = state->json["extensions"]; + } else { + state->json["extensions"] = extensions; + } + Dictionary lights_punctual; + extensions["KHR_lights_punctual"] = lights_punctual; + lights_punctual["lights"] = lights; + + print_verbose("glTF: Total lights: " + itos(state->lights.size())); + + return OK; +} + +Error GLTFDocument::_serialize_cameras(Ref state) { + Array cameras; + cameras.resize(state->cameras.size()); + for (GLTFCameraIndex i = 0; i < state->cameras.size(); i++) { + Dictionary d; + + Ref camera = state->cameras[i]; + + if (camera->get_perspective() == false) { + Dictionary og; + og["ymag"] = Math::deg2rad(camera->get_fov_size()); + og["xmag"] = Math::deg2rad(camera->get_fov_size()); + og["zfar"] = camera->get_zfar(); + og["znear"] = camera->get_znear(); + d["orthographic"] = og; + d["type"] = "orthographic"; + } else if (camera->get_perspective()) { + Dictionary ppt; + // GLTF spec is in radians, Godot's camera is in degrees. + ppt["yfov"] = Math::deg2rad(camera->get_fov_size()); + ppt["zfar"] = camera->get_zfar(); + ppt["znear"] = camera->get_znear(); + d["perspective"] = ppt; + d["type"] = "perspective"; + } + cameras[i] = d; + } + + if (!state->cameras.size()) { + return OK; + } + + state->json["cameras"] = cameras; + + print_verbose("glTF: Total cameras: " + itos(state->cameras.size())); + + return OK; +} + +Error GLTFDocument::_parse_lights(Ref state) { + if (!state->json.has("extensions")) { + return OK; + } + Dictionary extensions = state->json["extensions"]; + if (!extensions.has("KHR_lights_punctual")) { + return OK; + } + Dictionary lights_punctual = extensions["KHR_lights_punctual"]; + if (!lights_punctual.has("lights")) { + return OK; + } + + const Array &lights = lights_punctual["lights"]; + + for (GLTFLightIndex light_i = 0; light_i < lights.size(); light_i++) { + const Dictionary &d = lights[light_i]; + + Ref light; + light.instance(); + ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); + const String &type = d["type"]; + light->type = type; + + if (d.has("color")) { + const Array &arr = d["color"]; + ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); + light->color = c; + } + if (d.has("intensity")) { + light->intensity = d["intensity"]; + } + if (d.has("range")) { + light->range = d["range"]; + } + if (type == "spot") { + const Dictionary &spot = d["spot"]; + light->inner_cone_angle = spot["innerConeAngle"]; + light->outer_cone_angle = spot["outerConeAngle"]; + ERR_FAIL_COND_V_MSG(light->inner_cone_angle >= light->outer_cone_angle, ERR_PARSE_ERROR, "The inner angle must be smaller than the outer angle."); + } else if (type != "point" && type != "directional") { + ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Light type is unknown."); + } + + state->lights.push_back(light); + } + + print_verbose("glTF: Total lights: " + itos(state->lights.size())); + + return OK; +} + +Error GLTFDocument::_parse_cameras(Ref state) { + if (!state->json.has("cameras")) { + return OK; + } + + const Array cameras = state->json["cameras"]; + + for (GLTFCameraIndex i = 0; i < cameras.size(); i++) { + const Dictionary &d = cameras[i]; + + Ref camera; + camera.instance(); + ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); + const String &type = d["type"]; + if (type == "orthographic") { + camera->set_perspective(false); + if (d.has("orthographic")) { + const Dictionary &og = d["orthographic"]; + // GLTF spec is in radians, Godot's camera is in degrees. + camera->set_fov_size(Math::rad2deg(real_t(og["ymag"]))); + camera->set_zfar(og["zfar"]); + camera->set_znear(og["znear"]); + } else { + camera->set_fov_size(10); + } + } else if (type == "perspective") { + camera->set_perspective(true); + if (d.has("perspective")) { + const Dictionary &ppt = d["perspective"]; + // GLTF spec is in radians, Godot's camera is in degrees. + camera->set_fov_size(Math::rad2deg(real_t(ppt["yfov"]))); + camera->set_zfar(ppt["zfar"]); + camera->set_znear(ppt["znear"]); + } else { + camera->set_fov_size(10); + } + } else { + ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Camera should be in 'orthographic' or 'perspective'"); + } + + state->cameras.push_back(camera); + } + + print_verbose("glTF: Total cameras: " + itos(state->cameras.size())); + + return OK; +} + +String GLTFDocument::interpolation_to_string(const GLTFAnimation::Interpolation p_interp) { + String interp = "LINEAR"; + if (p_interp == GLTFAnimation::INTERP_STEP) { + interp = "STEP"; + } else if (p_interp == GLTFAnimation::INTERP_LINEAR) { + interp = "LINEAR"; + } else if (p_interp == GLTFAnimation::INTERP_CATMULLROMSPLINE) { + interp = "CATMULLROMSPLINE"; + } else if (p_interp == GLTFAnimation::INTERP_CUBIC_SPLINE) { + interp = "CUBICSPLINE"; + } + + return interp; +} + +Error GLTFDocument::_serialize_animations(Ref state) { + if (!state->animation_players.size()) { + return OK; + } + for (int32_t player_i = 0; player_i < state->animation_players.size(); player_i++) { + List animation_names; + AnimationPlayer *animation_player = state->animation_players[player_i]; + animation_player->get_animation_list(&animation_names); + if (animation_names.size()) { + for (int animation_name_i = 0; animation_name_i < animation_names.size(); animation_name_i++) { + _convert_animation(state, animation_player, animation_names[animation_name_i]); + } + } + } + Array animations; + for (GLTFAnimationIndex animation_i = 0; animation_i < state->animations.size(); animation_i++) { + Dictionary d; + Ref gltf_animation = state->animations[animation_i]; + if (!gltf_animation->get_tracks().size()) { + continue; + } + + if (!gltf_animation->get_name().empty()) { + d["name"] = gltf_animation->get_name(); + } + Array channels; + Array samplers; + + for (Map::Element *track_i = gltf_animation->get_tracks().front(); track_i; track_i = track_i->next()) { + GLTFAnimation::Track track = track_i->get(); + if (track.translation_track.times.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + s["interpolation"] = interpolation_to_string(track.translation_track.interpolation); + Vector times = Variant(track.translation_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Vector values = Variant(track.translation_track.values); + s["output"] = _encode_accessor_as_vec3(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "translation"; + target["node"] = track_i->key(); + + t["target"] = target; + channels.push_back(t); + } + if (track.rotation_track.times.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + s["interpolation"] = interpolation_to_string(track.rotation_track.interpolation); + Vector times = Variant(track.rotation_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Vector values = track.rotation_track.values; + s["output"] = _encode_accessor_as_quats(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "rotation"; + target["node"] = track_i->key(); + + t["target"] = target; + channels.push_back(t); + } + if (track.scale_track.times.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + s["interpolation"] = interpolation_to_string(track.scale_track.interpolation); + Vector times = Variant(track.scale_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Vector values = Variant(track.scale_track.values); + s["output"] = _encode_accessor_as_vec3(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "scale"; + target["node"] = track_i->key(); + + t["target"] = target; + channels.push_back(t); + } + if (track.weight_tracks.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + Vector times; + Vector values; + + for (int32_t times_i = 0; times_i < track.weight_tracks[0].times.size(); times_i++) { + real_t time = track.weight_tracks[0].times[times_i]; + times.push_back(time); + } + + values.resize(times.size() * track.weight_tracks.size()); + // TODO Sort by order in blend shapes + for (int k = 0; k < track.weight_tracks.size(); k++) { + Vector wdata = track.weight_tracks[k].values; + for (int l = 0; l < wdata.size(); l++) { + values.write[l * track.weight_tracks.size() + k] = wdata.write[l]; + } + } + + s["interpolation"] = interpolation_to_string(track.weight_tracks[track.weight_tracks.size() - 1].interpolation); + s["input"] = _encode_accessor_as_floats(state, times, false); + s["output"] = _encode_accessor_as_floats(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "weights"; + target["node"] = track_i->key(); + + t["target"] = target; + channels.push_back(t); + } + } + if (channels.size() && samplers.size()) { + d["channels"] = channels; + d["samplers"] = samplers; + animations.push_back(d); + } + } + + state->json["animations"] = animations; + + print_verbose("glTF: Total animations '" + itos(state->animations.size()) + "'."); + + return OK; +} + +Error GLTFDocument::_parse_animations(Ref state) { + if (!state->json.has("animations")) { + return OK; + } + + const Array &animations = state->json["animations"]; + + for (GLTFAnimationIndex i = 0; i < animations.size(); i++) { + const Dictionary &d = animations[i]; + + Ref animation; + animation.instance(); + + if (!d.has("channels") || !d.has("samplers")) { + continue; + } + + Array channels = d["channels"]; + Array samplers = d["samplers"]; + + if (d.has("name")) { + const String name = d["name"]; + if (name.begins_with("loop") || name.ends_with("loop") || name.begins_with("cycle") || name.ends_with("cycle")) { + animation->set_loop(true); + } + if (state->use_legacy_names) { + animation->set_name(_sanitize_scene_name(state, name)); + } else { + animation->set_name(_gen_unique_animation_name(state, name)); + } + } + + for (int j = 0; j < channels.size(); j++) { + const Dictionary &c = channels[j]; + if (!c.has("target")) { + continue; + } + + const Dictionary &t = c["target"]; + if (!t.has("node") || !t.has("path")) { + continue; + } + + ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR); + const int sampler = c["sampler"]; + ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR); + + GLTFNodeIndex node = t["node"]; + String path = t["path"]; + + ERR_FAIL_INDEX_V(node, state->nodes.size(), ERR_PARSE_ERROR); + + GLTFAnimation::Track *track = nullptr; + + if (!animation->get_tracks().has(node)) { + animation->get_tracks()[node] = GLTFAnimation::Track(); + } + + track = &animation->get_tracks()[node]; + + const Dictionary &s = samplers[sampler]; + + ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR); + + const int input = s["input"]; + const int output = s["output"]; + + GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR; + int output_count = 1; + if (s.has("interpolation")) { + const String &in = s["interpolation"]; + if (in == "STEP") { + interp = GLTFAnimation::INTERP_STEP; + } else if (in == "LINEAR") { + interp = GLTFAnimation::INTERP_LINEAR; + } else if (in == "CATMULLROMSPLINE") { + interp = GLTFAnimation::INTERP_CATMULLROMSPLINE; + output_count = 3; + } else if (in == "CUBICSPLINE") { + interp = GLTFAnimation::INTERP_CUBIC_SPLINE; + output_count = 3; + } + } + + const Vector times = _decode_accessor_as_floats(state, input, false); + if (path == "translation") { + const Vector translations = _decode_accessor_as_vec3(state, output, false); + track->translation_track.interpolation = interp; + track->translation_track.times = Variant(times); //convert via variant + track->translation_track.values = Variant(translations); //convert via variant + } else if (path == "rotation") { + const Vector rotations = _decode_accessor_as_quat(state, output, false); + track->rotation_track.interpolation = interp; + track->rotation_track.times = Variant(times); //convert via variant + track->rotation_track.values = rotations; + } else if (path == "scale") { + const Vector scales = _decode_accessor_as_vec3(state, output, false); + track->scale_track.interpolation = interp; + track->scale_track.times = Variant(times); //convert via variant + track->scale_track.values = Variant(scales); //convert via variant + } else if (path == "weights") { + const Vector weights = _decode_accessor_as_floats(state, output, false); + + ERR_FAIL_INDEX_V(state->nodes[node]->mesh, state->meshes.size(), ERR_PARSE_ERROR); + Ref mesh = state->meshes[state->nodes[node]->mesh]; + ERR_CONTINUE(!mesh->get_blend_weights().size()); + const int wc = mesh->get_blend_weights().size(); + + track->weight_tracks.resize(wc); + + const int expected_value_count = times.size() * output_count * wc; + ERR_FAIL_COND_V_MSG(weights.size() != expected_value_count, ERR_PARSE_ERROR, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead."); + + const int wlen = weights.size() / wc; + for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea + GLTFAnimation::Channel cf; + cf.interpolation = interp; + cf.times = Variant(times); + Vector wdata; + wdata.resize(wlen); + for (int l = 0; l < wlen; l++) { + wdata.write[l] = weights[l * wc + k]; + } + + cf.values = wdata; + track->weight_tracks.write[k] = cf; + } + } else { + WARN_PRINT("Invalid path '" + path + "'."); + } + } + + state->animations.push_back(animation); + } + + print_verbose("glTF: Total animations '" + itos(state->animations.size()) + "'."); + + return OK; +} + +void GLTFDocument::_assign_scene_names(Ref state) { + for (int i = 0; i < state->nodes.size(); i++) { + Ref n = state->nodes[i]; + + // Any joints get unique names generated when the skeleton is made, unique to the skeleton + if (n->skeleton >= 0) { + continue; + } + + if (n->get_name().empty()) { + if (n->mesh >= 0) { + n->set_name(_gen_unique_name(state, "Mesh")); + } else if (n->camera >= 0) { + n->set_name(_gen_unique_name(state, "Camera")); + } else { + n->set_name(_gen_unique_name(state, "Node")); + } + } + + n->set_name(_gen_unique_name(state, n->get_name())); + } +} + +BoneAttachment *GLTFDocument::_generate_bone_attachment(Ref state, Skeleton *skeleton, const GLTFNodeIndex node_index) { + Ref gltf_node = state->nodes[node_index]; + Ref bone_node = state->nodes[gltf_node->parent]; + + BoneAttachment *bone_attachment = memnew(BoneAttachment); + print_verbose("glTF: Creating bone attachment for: " + gltf_node->get_name()); + + ERR_FAIL_COND_V(!bone_node->joint, nullptr); + + bone_attachment->set_bone_name(bone_node->get_name()); + + return bone_attachment; +} + +GLTFMeshIndex GLTFDocument::_convert_mesh_instance(Ref state, MeshInstance *p_mesh_instance) { + ERR_FAIL_NULL_V(p_mesh_instance, -1); + if (p_mesh_instance->get_mesh().is_null()) { + return -1; + } + Ref import_mesh; + import_mesh.instance(); + Ref godot_mesh = p_mesh_instance->get_mesh(); + if (godot_mesh.is_null()) { + return -1; + } + Vector blend_weights; + Vector blend_names; + int32_t blend_count = godot_mesh->get_blend_shape_count(); + blend_names.resize(blend_count); + blend_weights.resize(blend_count); + for (int32_t blend_i = 0; blend_i < godot_mesh->get_blend_shape_count(); blend_i++) { + String blend_name = godot_mesh->get_blend_shape_name(blend_i); + blend_names.write[blend_i] = blend_name; + import_mesh->add_blend_shape(blend_name); + } + for (int32_t surface_i = 0; surface_i < godot_mesh->get_surface_count(); surface_i++) { + Mesh::PrimitiveType primitive_type = godot_mesh->surface_get_primitive_type(surface_i); + Array arrays = godot_mesh->surface_get_arrays(surface_i); + Array blend_shape_arrays = godot_mesh->surface_get_blend_shape_arrays(surface_i); + Ref mat = godot_mesh->surface_get_material(surface_i); + Ref godot_array_mesh = godot_mesh; + String surface_name; + if (godot_array_mesh.is_valid()) { + surface_name = godot_array_mesh->surface_get_name(surface_i); + } + if (p_mesh_instance->get_surface_material(surface_i).is_valid()) { + mat = p_mesh_instance->get_surface_material(surface_i); + } + if (p_mesh_instance->get_material_override().is_valid()) { + mat = p_mesh_instance->get_material_override(); + } + int32_t mat_idx = import_mesh->get_surface_count(); + import_mesh->add_surface_from_arrays(primitive_type, arrays, blend_shape_arrays); + import_mesh->surface_set_material(mat_idx, mat); + } + for (int32_t blend_i = 0; blend_i < blend_count; blend_i++) { + blend_weights.write[blend_i] = 0.0f; + } + Ref gltf_mesh; + gltf_mesh.instance(); + gltf_mesh->set_mesh(import_mesh); + gltf_mesh->set_blend_weights(blend_weights); + GLTFMeshIndex mesh_i = state->meshes.size(); + state->meshes.push_back(gltf_mesh); + return mesh_i; +} + +Spatial *GLTFDocument::_generate_mesh_instance(Ref state, Node *scene_parent, const GLTFNodeIndex node_index) { + Ref gltf_node = state->nodes[node_index]; + + ERR_FAIL_INDEX_V(gltf_node->mesh, state->meshes.size(), nullptr); + + MeshInstance *mi = memnew(MeshInstance); + print_verbose("glTF: Creating mesh for: " + gltf_node->get_name()); + + Ref mesh = state->meshes.write[gltf_node->mesh]; + if (mesh.is_null()) { + return mi; + } + Ref import_mesh = mesh->get_mesh(); + if (import_mesh.is_null()) { + return mi; + } + mi->set_mesh(import_mesh); + for (int i = 0; i < mesh->get_blend_weights().size(); i++) { + mi->set("blend_shapes/" + mesh->get_mesh()->get_blend_shape_name(i), mesh->get_blend_weights()[i]); + } + return mi; +} + +Light *GLTFDocument::_generate_light(Ref state, Node *scene_parent, const GLTFNodeIndex node_index) { + Ref gltf_node = state->nodes[node_index]; + + ERR_FAIL_INDEX_V(gltf_node->light, state->lights.size(), nullptr); + + print_verbose("glTF: Creating light for: " + gltf_node->get_name()); + + Ref l = state->lights[gltf_node->light]; + + float intensity = l->intensity; + if (intensity > 10) { + // GLTF spec has the default around 1, but Blender defaults lights to 100. + // The only sane way to handle this is to check where it came from and + // handle it accordingly. If it's over 10, it probably came from Blender. + intensity /= 100; + } + + if (l->type == "directional") { + DirectionalLight *light = memnew(DirectionalLight); + light->set_param(Light::PARAM_ENERGY, intensity); + light->set_color(l->color); + return light; + } + + const float range = CLAMP(l->range, 0, 4096); + // Doubling the range will double the effective brightness, so we need double attenuation (half brightness). + // We want to have double intensity give double brightness, so we need half the attenuation. + const float attenuation = range / intensity; + if (l->type == "point") { + OmniLight *light = memnew(OmniLight); + light->set_param(OmniLight::PARAM_ATTENUATION, attenuation); + light->set_param(OmniLight::PARAM_RANGE, range); + light->set_color(l->color); + return light; + } + if (l->type == "spot") { + SpotLight *light = memnew(SpotLight); + light->set_param(SpotLight::PARAM_ATTENUATION, attenuation); + light->set_param(SpotLight::PARAM_RANGE, range); + light->set_param(SpotLight::PARAM_SPOT_ANGLE, Math::rad2deg(l->outer_cone_angle)); + light->set_color(l->color); + + // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b + // The points in desmos are not exact, except for (1, infinity). + float angle_ratio = l->inner_cone_angle / l->outer_cone_angle; + float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1; + light->set_param(SpotLight::PARAM_SPOT_ATTENUATION, angle_attenuation); + return light; + } + return nullptr; +} + +Camera *GLTFDocument::_generate_camera(Ref state, Node *scene_parent, const GLTFNodeIndex node_index) { + Ref gltf_node = state->nodes[node_index]; + + ERR_FAIL_INDEX_V(gltf_node->camera, state->cameras.size(), nullptr); + + Camera *camera = memnew(Camera); + print_verbose("glTF: Creating camera for: " + gltf_node->get_name()); + + Ref c = state->cameras[gltf_node->camera]; + if (c->get_perspective()) { + camera->set_perspective(c->get_fov_size(), c->get_znear(), c->get_zfar()); + } else { + camera->set_orthogonal(c->get_fov_size(), c->get_znear(), c->get_zfar()); + } + + return camera; +} + +GLTFCameraIndex GLTFDocument::_convert_camera(Ref state, Camera *p_camera) { + print_verbose("glTF: Converting camera: " + p_camera->get_name()); + + Ref c; + c.instance(); + + if (p_camera->get_projection() == Camera::Projection::PROJECTION_PERSPECTIVE) { + c->set_perspective(true); + c->set_fov_size(p_camera->get_fov()); + c->set_zfar(p_camera->get_zfar()); + c->set_znear(p_camera->get_znear()); + } else { + c->set_fov_size(p_camera->get_fov()); + c->set_zfar(p_camera->get_zfar()); + c->set_znear(p_camera->get_znear()); + } + GLTFCameraIndex camera_index = state->cameras.size(); + state->cameras.push_back(c); + return camera_index; +} + +GLTFLightIndex GLTFDocument::_convert_light(Ref state, Light *p_light) { + print_verbose("glTF: Converting light: " + p_light->get_name()); + + Ref l; + l.instance(); + l->color = p_light->get_color(); + if (cast_to(p_light)) { + l->type = "directional"; + DirectionalLight *light = cast_to(p_light); + l->intensity = light->get_param(DirectionalLight::PARAM_ENERGY); + l->range = FLT_MAX; // Range for directional lights is infinite in Godot. + } else if (cast_to(p_light)) { + l->type = "point"; + OmniLight *light = cast_to(p_light); + l->range = light->get_param(OmniLight::PARAM_RANGE); + float attenuation = p_light->get_param(OmniLight::PARAM_ATTENUATION); + l->intensity = l->range / attenuation; + } else if (cast_to(p_light)) { + l->type = "spot"; + SpotLight *light = cast_to(p_light); + l->range = light->get_param(SpotLight::PARAM_RANGE); + float attenuation = light->get_param(SpotLight::PARAM_ATTENUATION); + l->intensity = l->range / attenuation; + l->outer_cone_angle = Math::deg2rad(light->get_param(SpotLight::PARAM_SPOT_ANGLE)); + + // This equation is the inverse of the import equation (which has a desmos link). + float angle_ratio = 1 - (0.2 / (0.1 + light->get_param(SpotLight::PARAM_SPOT_ATTENUATION))); + angle_ratio = MAX(0, angle_ratio); + l->inner_cone_angle = l->outer_cone_angle * angle_ratio; + } + + GLTFLightIndex light_index = state->lights.size(); + state->lights.push_back(l); + return light_index; +} + +GLTFSkeletonIndex GLTFDocument::_convert_skeleton(Ref state, Skeleton *p_skeleton) { + print_verbose("glTF: Converting skeleton: " + p_skeleton->get_name()); + Ref gltf_skeleton; + gltf_skeleton.instance(); + gltf_skeleton->set_name(_gen_unique_name(state, p_skeleton->get_name())); + gltf_skeleton->godot_skeleton = p_skeleton; + GLTFSkeletonIndex skeleton_i = state->skeletons.size(); + state->skeletons.push_back(gltf_skeleton); + return skeleton_i; +} + +void GLTFDocument::_convert_spatial(Ref state, Spatial *p_spatial, Ref p_node) { + Transform xform = p_spatial->get_transform(); + p_node->scale = xform.basis.get_scale(); + p_node->rotation = xform.basis.get_rotation_quat(); + p_node->translation = xform.origin; +} + +Spatial *GLTFDocument::_generate_spatial(Ref state, Node *scene_parent, const GLTFNodeIndex node_index) { + Ref gltf_node = state->nodes[node_index]; + + Spatial *spatial = memnew(Spatial); + print_verbose("glTF: Converting spatial: " + gltf_node->get_name()); + + return spatial; +} +void GLTFDocument::_convert_scene_node(Ref state, Node *p_current, Node *p_root, const GLTFNodeIndex p_gltf_parent, const GLTFNodeIndex p_gltf_root) { + bool retflag = true; + _check_visibility(p_current, retflag); + if (retflag) { + return; + } + Ref gltf_node; + gltf_node.instance(); + gltf_node->set_name(_gen_unique_name(state, p_current->get_name())); + if (cast_to(p_current)) { + Spatial *spatial = cast_to(p_current); + _convert_spatial(state, spatial, gltf_node); + } + if (cast_to(p_current)) { + Spatial *spatial = cast_to(p_current); + _convert_mesh_to_gltf(p_current, state, spatial, gltf_node); + } else if (cast_to(p_current)) { + _convert_bone_attachment_to_gltf(p_current, state, gltf_node, retflag); + // TODO 2020-12-21 iFire Handle the case of objects under the bone attachment. + return; + } else if (cast_to(p_current)) { + _convert_skeleton_to_gltf(p_current, state, p_gltf_parent, p_gltf_root, gltf_node, p_root); + // We ignore the Godot Engine node that is the skeleton. + return; + } else if (cast_to(p_current)) { + _convert_mult_mesh_instance_to_gltf(p_current, p_gltf_parent, p_gltf_root, gltf_node, state, p_root); +#ifdef MODULE_CSG_ENABLED + } else if (cast_to(p_current)) { + if (p_current->get_parent() && cast_to(p_current)->is_root_shape()) { + _convert_csg_shape_to_gltf(p_current, p_gltf_parent, gltf_node, state); + } +#endif // MODULE_CSG_ENABLED +#ifdef MODULE_GRIDMAP_ENABLED + } else if (cast_to(p_current)) { + _convert_grid_map_to_gltf(p_current, p_gltf_parent, p_gltf_root, gltf_node, state, p_root); +#endif // MODULE_GRIDMAP_ENABLED + } else if (cast_to(p_current)) { + Camera *camera = Object::cast_to(p_current); + _convert_camera_to_gltf(camera, state, camera, gltf_node); + } else if (cast_to(p_current)) { + Light *light = Object::cast_to(p_current); + _convert_light_to_gltf(light, state, light, gltf_node); + } else if (cast_to(p_current)) { + AnimationPlayer *animation_player = Object::cast_to(p_current); + _convert_animation_player_to_gltf(animation_player, state, p_gltf_parent, p_gltf_root, gltf_node, p_current, p_root); + } + GLTFNodeIndex current_node_i = state->nodes.size(); + GLTFNodeIndex gltf_root = p_gltf_root; + if (gltf_root == -1) { + gltf_root = current_node_i; + Array scenes; + scenes.push_back(gltf_root); + state->json["scene"] = scenes; + } + _create_gltf_node(state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node); + for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) { + _convert_scene_node(state, p_current->get_child(node_i), p_root, current_node_i, gltf_root); + } +} + +#ifdef MODULE_CSG_ENABLED +void GLTFDocument::_convert_csg_shape_to_gltf(Node *p_current, GLTFNodeIndex p_gltf_parent, Ref gltf_node, Ref state) { + CSGShape *csg = Object::cast_to(p_current); + csg->call("_update_shape"); + Array meshes = csg->get_meshes(); + if (meshes.size() != 2) { + return; + } + Ref mat; + if (csg->get_material_override().is_valid()) { + mat = csg->get_material_override(); + } + Ref gltf_mesh; + gltf_mesh.instance(); + Ref import_mesh; + import_mesh.instance(); + Ref array_mesh = csg->get_meshes()[1]; + for (int32_t surface_i = 0; surface_i < array_mesh->get_surface_count(); surface_i++) { + import_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array_mesh->surface_get_arrays(surface_i)); + } + gltf_mesh->set_mesh(import_mesh); + GLTFMeshIndex mesh_i = state->meshes.size(); + state->meshes.push_back(gltf_mesh); + gltf_node->mesh = mesh_i; + gltf_node->xform = csg->get_meshes()[0]; + gltf_node->set_name(_gen_unique_name(state, csg->get_name())); +} +#endif // MODULE_CSG_ENABLED + +void GLTFDocument::_create_gltf_node(Ref state, Node *p_scene_parent, GLTFNodeIndex current_node_i, + GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, Ref gltf_node) { + state->scene_nodes.insert(current_node_i, p_scene_parent); + state->nodes.push_back(gltf_node); + if (current_node_i == p_parent_node_index) { + return; + } + if (p_parent_node_index == -1) { + return; + } + state->nodes.write[p_parent_node_index]->children.push_back(current_node_i); +} + +void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *animation_player, Ref state, const GLTFNodeIndex &p_gltf_current, const GLTFNodeIndex &p_gltf_root_index, Ref p_gltf_node, Node *p_scene_parent, Node *p_root) { + ERR_FAIL_COND(!animation_player); + state->animation_players.push_back(animation_player); + print_verbose(String("glTF: Converting animation player: ") + animation_player->get_name()); +} + +void GLTFDocument::_check_visibility(Node *p_node, bool &retflag) { + retflag = true; + Spatial *spatial = Object::cast_to(p_node); + Node2D *node_2d = Object::cast_to(p_node); + if (node_2d && !node_2d->is_visible()) { + return; + } + if (spatial && !spatial->is_visible()) { + return; + } + retflag = false; +} + +void GLTFDocument::_convert_camera_to_gltf(Camera *camera, Ref state, Spatial *spatial, Ref gltf_node) { + ERR_FAIL_COND(!camera); + GLTFCameraIndex camera_index = _convert_camera(state, camera); + if (camera_index != -1) { + gltf_node->camera = camera_index; + } +} + +void GLTFDocument::_convert_light_to_gltf(Light *light, Ref state, Spatial *spatial, Ref gltf_node) { + ERR_FAIL_COND(!light); + GLTFLightIndex light_index = _convert_light(state, light); + if (light_index != -1) { + gltf_node->light = light_index; + } +} + +#ifdef MODULE_GRIDMAP_ENABLED +void GLTFDocument::_convert_grid_map_to_gltf(Node *p_scene_parent, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref gltf_node, Ref state, Node *p_root_node) { + GridMap *grid_map = Object::cast_to(p_scene_parent); + ERR_FAIL_COND(!grid_map); + Array cells = grid_map->get_used_cells(); + for (int32_t k = 0; k < cells.size(); k++) { + GLTFNode *new_gltf_node = memnew(GLTFNode); + gltf_node->children.push_back(state->nodes.size()); + state->nodes.push_back(new_gltf_node); + Vector3 cell_location = cells[k]; + int32_t cell = grid_map->get_cell_item( + Vector3(cell_location.x, cell_location.y, cell_location.z)); + MeshInstance *import_mesh_node = memnew(MeshInstance); + import_mesh_node->set_mesh(grid_map->get_mesh_library()->get_item_mesh(cell)); + Transform cell_xform; + cell_xform.basis.set_orthogonal_index( + grid_map->get_cell_item_orientation( + Vector3(cell_location.x, cell_location.y, cell_location.z))); + cell_xform.basis.scale(Vector3(grid_map->get_cell_scale(), + grid_map->get_cell_scale(), + grid_map->get_cell_scale())); + cell_xform.set_origin(grid_map->map_to_world( + Vector3(cell_location.x, cell_location.y, cell_location.z))); + Ref gltf_mesh; + gltf_mesh.instance(); + gltf_mesh = import_mesh_node; + new_gltf_node->mesh = state->meshes.size(); + state->meshes.push_back(gltf_mesh); + new_gltf_node->xform = cell_xform * grid_map->get_transform(); + new_gltf_node->set_name(_gen_unique_name(state, grid_map->get_mesh_library()->get_item_name(cell))); + } +} +#endif // MODULE_GRIDMAP_ENABLED + +void GLTFDocument::_convert_mult_mesh_instance_to_gltf(Node *p_scene_parent, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref gltf_node, Ref state, Node *p_root_node) { + MultiMeshInstance *multi_mesh_instance = Object::cast_to(p_scene_parent); + ERR_FAIL_COND(!multi_mesh_instance); + Ref multi_mesh = multi_mesh_instance->get_multimesh(); + if (multi_mesh.is_valid()) { + for (int32_t instance_i = 0; instance_i < multi_mesh->get_instance_count(); + instance_i++) { + GLTFNode *new_gltf_node = memnew(GLTFNode); + Transform transform; + if (multi_mesh->get_transform_format() == MultiMesh::TRANSFORM_2D) { + Transform2D xform_2d = multi_mesh->get_instance_transform_2d(instance_i); + transform.origin = + Vector3(xform_2d.get_origin().x, 0, xform_2d.get_origin().y); + real_t rotation = xform_2d.get_rotation(); + Quat quat(Vector3(0, 1, 0), rotation); + Size2 scale = xform_2d.get_scale(); + transform.basis.set_quat_scale(quat, + Vector3(scale.x, 0, scale.y)); + transform = + multi_mesh_instance->get_transform() * transform; + } else if (multi_mesh->get_transform_format() == MultiMesh::TRANSFORM_3D) { + transform = multi_mesh_instance->get_transform() * + multi_mesh->get_instance_transform(instance_i); + } + Ref mm = multi_mesh->get_mesh(); + if (mm.is_valid()) { + Ref mesh; + mesh.instance(); + for (int32_t surface_i = 0; surface_i < mm->get_surface_count(); surface_i++) { + Array surface = mm->surface_get_arrays(surface_i); + mesh->add_surface_from_arrays(mm->surface_get_primitive_type(surface_i), surface); + } + Ref gltf_mesh; + gltf_mesh.instance(); + gltf_mesh->set_name(multi_mesh->get_name()); + gltf_mesh->set_mesh(mesh); + new_gltf_node->mesh = state->meshes.size(); + state->meshes.push_back(gltf_mesh); + } + new_gltf_node->xform = transform; + new_gltf_node->set_name(_gen_unique_name(state, multi_mesh_instance->get_name())); + gltf_node->children.push_back(state->nodes.size()); + state->nodes.push_back(new_gltf_node); + } + } +} + +void GLTFDocument::_convert_skeleton_to_gltf(Node *p_scene_parent, Ref state, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref gltf_node, Node *p_root_node) { + Skeleton *skeleton = Object::cast_to(p_scene_parent); + if (skeleton) { + // Remove placeholder skeleton3d node by not creating the gltf node + // Skins are per mesh + for (int node_i = 0; node_i < skeleton->get_child_count(); node_i++) { + _convert_scene_node(state, skeleton->get_child(node_i), p_root_node, p_parent_node_index, p_root_node_index); + } + } +} + +void GLTFDocument::_convert_bone_attachment_to_gltf(Node *p_scene_parent, Ref state, Ref gltf_node, bool &retflag) { + retflag = true; + BoneAttachment *bone_attachment = Object::cast_to(p_scene_parent); + if (bone_attachment) { + Node *node = bone_attachment->get_parent(); + while (node) { + Skeleton *bone_attachment_skeleton = Object::cast_to(node); + if (bone_attachment_skeleton) { + for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) { + if (state->skeletons[skeleton_i]->godot_skeleton != bone_attachment_skeleton) { + continue; + } + state->skeletons.write[skeleton_i]->bone_attachments.push_back(bone_attachment); + break; + } + break; + } + node = node->get_parent(); + } + gltf_node.unref(); + return; + } + retflag = false; +} + +void GLTFDocument::_convert_mesh_to_gltf(Node *p_scene_parent, Ref state, Spatial *spatial, Ref gltf_node) { + MeshInstance *mi = Object::cast_to(p_scene_parent); + if (mi) { + GLTFMeshIndex gltf_mesh_index = _convert_mesh_instance(state, mi); + if (gltf_mesh_index != -1) { + gltf_node->mesh = gltf_mesh_index; + } + } +} + +void GLTFDocument::_generate_scene_node(Ref state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) { + Ref gltf_node = state->nodes[node_index]; + + Spatial *current_node = nullptr; + + // Is our parent a skeleton + Skeleton *active_skeleton = Object::cast_to(scene_parent); + + if (gltf_node->skeleton >= 0) { + Skeleton *skeleton = state->skeletons[gltf_node->skeleton]->godot_skeleton; + + if (active_skeleton != skeleton) { + ERR_FAIL_COND_MSG(active_skeleton != nullptr, "glTF: Generating scene detected direct parented Skeletons"); + + // 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); + } + } + + active_skeleton = skeleton; + current_node = skeleton; + } + + // If we have an active skeleton, and the node is node skinned, we need to create a bone attachment + if (current_node == nullptr || gltf_node->mesh >= 0 || gltf_node->camera >= 0 || gltf_node->light >= 0) { + if (active_skeleton != nullptr && gltf_node->skin < 0) { + BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index); + + scene_parent->add_child(bone_attachment); + bone_attachment->set_owner(scene_root); + + // There is no gltf_node that represent this, so just directly create a unique name + bone_attachment->set_name(_gen_unique_name(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; + } + + // We still have not managed to make a node + if (gltf_node->mesh >= 0) { + current_node = _generate_mesh_instance(state, scene_parent, node_index); + } else if (gltf_node->camera >= 0) { + current_node = _generate_camera(state, scene_parent, node_index); + } else if (gltf_node->light >= 0) { + current_node = _generate_light(state, scene_parent, node_index); + } + + if (!current_node) { + current_node = _generate_spatial(state, scene_parent, node_index); + } + + scene_parent->add_child(current_node); + if (current_node != scene_root) { + current_node->set_owner(scene_root); + } + if (use_xform) { + current_node->set_transform(gltf_node->xform); + } + if (state->use_legacy_names) { + current_node->set_name(_legacy_validate_node_name(gltf_node->get_name())); + } else { + current_node->set_name(gltf_node->get_name()); + } + } + + state->scene_nodes.insert(node_index, current_node); + + for (int i = 0; i < gltf_node->children.size(); ++i) { + _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]); + } +} + +template +struct EditorSceneImporterGLTFInterpolate { + T lerp(const T &a, const T &b, float c) const { + return a + (b - a) * c; + } + + T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) { + const float t2 = t * t; + const float t3 = t2 * t; + + return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + } + + T bezier(T start, T control_1, T control_2, T end, float t) { + /* Formula from Wikipedia article on Bezier curves. */ + const real_t omt = (1.0 - t); + const real_t omt2 = omt * omt; + const real_t omt3 = omt2 * omt; + const real_t t2 = t * t; + const real_t t3 = t2 * t; + + return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; + } +}; + +// thank you for existing, partial specialization +template <> +struct EditorSceneImporterGLTFInterpolate { + Quat lerp(const Quat &a, const Quat &b, const float c) const { + ERR_FAIL_COND_V_MSG(!a.is_normalized(), Quat(), "The quaternion \"a\" must be normalized."); + ERR_FAIL_COND_V_MSG(!b.is_normalized(), Quat(), "The quaternion \"b\" must be normalized."); + + return a.slerp(b, c).normalized(); + } + + Quat catmull_rom(const Quat &p0, const Quat &p1, const Quat &p2, const Quat &p3, const float c) { + ERR_FAIL_COND_V_MSG(!p1.is_normalized(), Quat(), "The quaternion \"p1\" must be normalized."); + ERR_FAIL_COND_V_MSG(!p2.is_normalized(), Quat(), "The quaternion \"p2\" must be normalized."); + + return p1.slerp(p2, c).normalized(); + } + + Quat bezier(const Quat start, const Quat control_1, const Quat control_2, const Quat end, const float t) { + ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quat(), "The start quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quat(), "The end quaternion must be normalized."); + + return start.slerp(end, t).normalized(); + } +}; + +template +T GLTFDocument::_interpolate_track(const Vector &p_times, const Vector &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { + //could use binary search, worth it? + int idx = -1; + for (int i = 0; i < p_times.size(); i++) { + if (p_times[i] > p_time) { + break; + } + idx++; + } + + EditorSceneImporterGLTFInterpolate interp; + + switch (p_interp) { + case GLTFAnimation::INTERP_LINEAR: { + if (idx == -1) { + return p_values[0]; + } else if (idx >= p_times.size() - 1) { + return p_values[p_times.size() - 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + return interp.lerp(p_values[idx], p_values[idx + 1], c); + } break; + case GLTFAnimation::INTERP_STEP: { + if (idx == -1) { + return p_values[0]; + } else if (idx >= p_times.size() - 1) { + return p_values[p_times.size() - 1]; + } + + return p_values[idx]; + } break; + case GLTFAnimation::INTERP_CATMULLROMSPLINE: { + if (idx == -1) { + return p_values[1]; + } else if (idx >= p_times.size() - 1) { + return p_values[1 + p_times.size() - 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + return interp.catmull_rom(p_values[idx - 1], p_values[idx], p_values[idx + 1], p_values[idx + 3], c); + } break; + case GLTFAnimation::INTERP_CUBIC_SPLINE: { + if (idx == -1) { + return p_values[1]; + } else if (idx >= p_times.size() - 1) { + return p_values[(p_times.size() - 1) * 3 + 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + const T from = p_values[idx * 3 + 1]; + const T c1 = from + p_values[idx * 3 + 2]; + const T to = p_values[idx * 3 + 4]; + const T c2 = to + p_values[idx * 3 + 3]; + + return interp.bezier(from, c1, c2, to, c); + } break; + } + + ERR_FAIL_V(p_values[0]); +} + +void GLTFDocument::_import_animation(Ref state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) { + Ref anim = state->animations[index]; + + String name = anim->get_name(); + if (name.empty()) { + // No node represent these, and they are not in the hierarchy, so just make a unique name + name = _gen_unique_name(state, "Animation"); + } + + Ref animation; + animation.instance(); + animation->set_name(name); + + if (anim->get_loop()) { + animation->set_loop(true); + } + + float length = 0.0; + + for (Map::Element *track_i = anim->get_tracks().front(); track_i; track_i = track_i->next()) { + const GLTFAnimation::Track &track = track_i->get(); + //need to find the path + NodePath node_path; + + GLTFNodeIndex node_index = track_i->key(); + if (state->nodes[node_index]->fake_joint_parent >= 0) { + // Should be same as parent + node_index = state->nodes[node_index]->fake_joint_parent; + } + + const Ref gltf_node = state->nodes[track_i->key()]; + + if (gltf_node->skeleton >= 0) { + const Skeleton *sk = state->skeletons[gltf_node->skeleton]->godot_skeleton; + ERR_FAIL_COND(sk == nullptr); + + const String path = ap->get_parent()->get_path_to(sk); + const String bone = gltf_node->get_name(); + node_path = path + ":" + bone; + } else { + Node *root = ap->get_parent(); + Node *godot_node = state->scene_nodes.find(node_index)->get(); + node_path = root->get_path_to(godot_node); + } + + for (int i = 0; i < track.rotation_track.times.size(); i++) { + length = MAX(length, track.rotation_track.times[i]); + } + for (int i = 0; i < track.translation_track.times.size(); i++) { + length = MAX(length, track.translation_track.times[i]); + } + for (int i = 0; i < track.scale_track.times.size(); i++) { + length = MAX(length, track.scale_track.times[i]); + } + + for (int i = 0; i < track.weight_tracks.size(); i++) { + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + length = MAX(length, track.weight_tracks[i].times[j]); + } + } + + if (track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) { + //make transform track + int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_TRANSFORM); + animation->track_set_path(track_idx, node_path); + //first determine animation length + + const double increment = 1.0 / bake_fps; + double time = 0.0; + + Vector3 base_pos; + Quat base_rot; + Vector3 base_scale = Vector3(1, 1, 1); + + if (!track.rotation_track.values.size()) { + base_rot = state->nodes[track_i->key()]->rotation.normalized(); + } + + if (!track.translation_track.values.size()) { + base_pos = state->nodes[track_i->key()]->translation; + } + + if (!track.scale_track.values.size()) { + base_scale = state->nodes[track_i->key()]->scale; + } + + bool last = false; + while (true) { + Vector3 pos = base_pos; + Quat rot = base_rot; + Vector3 scale = base_scale; + + if (track.translation_track.times.size()) { + pos = _interpolate_track(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation); + } + + if (track.rotation_track.times.size()) { + rot = _interpolate_track(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); + } + + if (track.scale_track.times.size()) { + scale = _interpolate_track(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); + } + + if (gltf_node->skeleton >= 0) { + Transform xform; + xform.basis.set_quat_scale(rot, scale); + xform.origin = pos; + + const Skeleton *skeleton = state->skeletons[gltf_node->skeleton]->godot_skeleton; + const int bone_idx = skeleton->find_bone(gltf_node->get_name()); + xform = skeleton->get_bone_rest(bone_idx).affine_inverse() * xform; + + rot = xform.basis.get_rotation_quat(); + rot.normalize(); + scale = xform.basis.get_scale(); + pos = xform.origin; + } + + animation->transform_track_insert_key(track_idx, time, pos, rot, scale); + + if (last) { + break; + } + time += increment; + if (time >= length) { + last = true; + time = length; + } + } + } + + for (int i = 0; i < track.weight_tracks.size(); i++) { + ERR_CONTINUE(gltf_node->mesh < 0 || gltf_node->mesh >= state->meshes.size()); + Ref mesh = state->meshes[gltf_node->mesh]; + ERR_CONTINUE(mesh.is_null()); + ERR_CONTINUE(mesh->get_mesh().is_null()); + const String prop = "blend_shapes/" + mesh->get_mesh()->get_blend_shape_name(i); + + const String blend_path = String(node_path) + ":" + prop; + + const int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_VALUE); + animation->track_set_path(track_idx, blend_path); + + // Only LINEAR and STEP (NEAREST) can be supported out of the box by Godot's Animation, + // the other modes have to be baked. + GLTFAnimation::Interpolation gltf_interp = track.weight_tracks[i].interpolation; + if (gltf_interp == GLTFAnimation::INTERP_LINEAR || gltf_interp == GLTFAnimation::INTERP_STEP) { + animation->track_set_interpolation_type(track_idx, gltf_interp == GLTFAnimation::INTERP_STEP ? Animation::INTERPOLATION_NEAREST : Animation::INTERPOLATION_LINEAR); + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + const float t = track.weight_tracks[i].times[j]; + const float attribs = track.weight_tracks[i].values[j]; + animation->track_insert_key(track_idx, t, attribs); + } + } else { + // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. + const double increment = 1.0 / bake_fps; + double time = 0.0; + bool last = false; + while (true) { + _interpolate_track(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); + if (last) { + break; + } + time += increment; + if (time >= length) { + last = true; + time = length; + } + } + } + } + } + + animation->set_length(length); + + ap->add_animation(name, animation); +} + +void GLTFDocument::_convert_mesh_instances(Ref state) { + for (GLTFNodeIndex mi_node_i = 0; mi_node_i < state->nodes.size(); ++mi_node_i) { + Ref node = state->nodes[mi_node_i]; + + if (node->mesh < 0) { + continue; + } + Array json_skins; + if (state->json.has("skins")) { + json_skins = state->json["skins"]; + } + Map::Element *mi_element = state->scene_nodes.find(mi_node_i); + if (!mi_element) { + continue; + } + MeshInstance *mi = Object::cast_to(mi_element->get()); + ERR_CONTINUE(!mi); + Transform mi_xform = mi->get_transform(); + node->scale = mi_xform.basis.get_scale(); + node->rotation = mi_xform.basis.get_rotation_quat(); + node->translation = mi_xform.origin; + + Dictionary json_skin; + Skeleton *skeleton = Object::cast_to(mi->get_node(mi->get_skeleton_path())); + if (!skeleton) { + continue; + } + if (!skeleton->get_bone_count()) { + continue; + } + Ref skin = mi->get_skin(); + if (skin.is_null()) { + skin = skeleton->register_skin(nullptr)->get_skin(); + } + Ref gltf_skin; + gltf_skin.instance(); + Array json_joints; + GLTFSkeletonIndex skeleton_gltf_i = -1; + + NodePath skeleton_path = mi->get_skeleton_path(); + bool is_unique = true; + for (int32_t skin_i = 0; skin_i < state->skins.size(); skin_i++) { + Ref prev_gltf_skin = state->skins.write[skin_i]; + if (gltf_skin.is_null()) { + continue; + } + GLTFSkeletonIndex prev_skeleton = prev_gltf_skin->get_skeleton(); + if (prev_skeleton == -1 || prev_skeleton >= state->skeletons.size()) { + continue; + } + if (prev_gltf_skin->get_godot_skin() == skin && state->skeletons[prev_skeleton]->godot_skeleton == skeleton) { + node->skin = skin_i; + node->skeleton = prev_skeleton; + is_unique = false; + break; + } + } + if (!is_unique) { + continue; + } + GLTFSkeletonIndex skeleton_i = _convert_skeleton(state, skeleton); + skeleton_gltf_i = skeleton_i; + ERR_CONTINUE(skeleton_gltf_i == -1); + gltf_skin->skeleton = skeleton_gltf_i; + Ref gltf_skeleton = state->skeletons.write[skeleton_gltf_i]; + for (int32_t bind_i = 0; bind_i < skin->get_bind_count(); bind_i++) { + String godot_bone_name = skin->get_bind_name(bind_i); + if (godot_bone_name.empty()) { + int32_t bone = skin->get_bind_bone(bind_i); + godot_bone_name = skeleton->get_bone_name(bone); + } + if (skeleton->find_bone(godot_bone_name) == -1) { + godot_bone_name = skeleton->get_bone_name(0); + } + BoneId bone_index = skeleton->find_bone(godot_bone_name); + ERR_CONTINUE(bone_index == -1); + Ref joint_node; + joint_node.instance(); + String gltf_bone_name = _gen_unique_bone_name(state, skeleton_gltf_i, godot_bone_name); + joint_node->set_name(gltf_bone_name); + + Transform bone_rest_xform = skeleton->get_bone_rest(bone_index); + joint_node->scale = bone_rest_xform.basis.get_scale(); + joint_node->rotation = bone_rest_xform.basis.get_rotation_quat(); + joint_node->translation = bone_rest_xform.origin; + joint_node->joint = true; + + int32_t joint_node_i = state->nodes.size(); + state->nodes.push_back(joint_node); + gltf_skeleton->godot_bone_node.insert(bone_index, joint_node_i); + int32_t joint_index = gltf_skin->joints.size(); + gltf_skin->joint_i_to_bone_i.insert(joint_index, bone_index); + gltf_skin->joints.push_back(joint_node_i); + gltf_skin->joints_original.push_back(joint_node_i); + gltf_skin->inverse_binds.push_back(skin->get_bind_pose(bind_i)); + json_joints.push_back(joint_node_i); + for (Map::Element *skin_scene_node_i = state->scene_nodes.front(); skin_scene_node_i; skin_scene_node_i = skin_scene_node_i->next()) { + if (skin_scene_node_i->get() == skeleton) { + gltf_skin->skin_root = skin_scene_node_i->key(); + json_skin["skeleton"] = skin_scene_node_i->key(); + } + } + gltf_skin->godot_skin = skin; + gltf_skin->set_name(_gen_unique_name(state, skin->get_name())); + } + for (int32_t bind_i = 0; bind_i < skin->get_bind_count(); bind_i++) { + String bone_name = skeleton->get_bone_name(bind_i); + String godot_bone_name = skin->get_bind_name(bind_i); + int32_t bone = -1; + if (skin->get_bind_bone(bind_i) != -1) { + bone = skin->get_bind_bone(bind_i); + godot_bone_name = skeleton->get_bone_name(bone); + } + bone = skeleton->find_bone(godot_bone_name); + if (bone == -1) { + continue; + } + BoneId bone_parent = skeleton->get_bone_parent(bone); + GLTFNodeIndex joint_node_i = gltf_skeleton->godot_bone_node[bone]; + ERR_CONTINUE(joint_node_i >= state->nodes.size()); + if (bone_parent != -1) { + GLTFNodeIndex parent_joint_gltf_node = gltf_skin->joints[bone_parent]; + Ref parent_joint_node = state->nodes.write[parent_joint_gltf_node]; + parent_joint_node->children.push_back(joint_node_i); + } else { + Node *node_parent = skeleton->get_parent(); + ERR_CONTINUE(!node_parent); + for (Map::Element *E = state->scene_nodes.front(); E; E = E->next()) { + if (E->get() == node_parent) { + GLTFNodeIndex gltf_node_i = E->key(); + Ref gltf_node = state->nodes.write[gltf_node_i]; + gltf_node->children.push_back(joint_node_i); + break; + } + } + } + } + _expand_skin(state, gltf_skin); + node->skin = state->skins.size(); + state->skins.push_back(gltf_skin); + + json_skin["inverseBindMatrices"] = _encode_accessor_as_xform(state, gltf_skin->inverse_binds, false); + json_skin["joints"] = json_joints; + json_skin["name"] = gltf_skin->get_name(); + json_skins.push_back(json_skin); + state->json["skins"] = json_skins; + } +} + +float GLTFDocument::solve_metallic(float p_dielectric_specular, float diffuse, float specular, float p_one_minus_specular_strength) { + if (specular <= p_dielectric_specular) { + return 0.0f; + } + + const float a = p_dielectric_specular; + const float b = diffuse * p_one_minus_specular_strength / (1.0f - p_dielectric_specular) + specular - 2.0f * p_dielectric_specular; + const float c = p_dielectric_specular - specular; + const float D = b * b - 4.0f * a * c; + return CLAMP((-b + Math::sqrt(D)) / (2.0f * a), 0.0f, 1.0f); +} + +float GLTFDocument::get_perceived_brightness(const Color p_color) { + const Color coeff = Color(R_BRIGHTNESS_COEFF, G_BRIGHTNESS_COEFF, B_BRIGHTNESS_COEFF); + const Color value = coeff * (p_color * p_color); + + const float r = value.r; + const float g = value.g; + const float b = value.b; + + return Math::sqrt(r + g + b); +} + +float GLTFDocument::get_max_component(const Color &p_color) { + const float r = p_color.r; + const float g = p_color.g; + const float b = p_color.b; + + return MAX(MAX(r, g), b); +} + +void GLTFDocument::_process_mesh_instances(Ref state, Node *scene_root) { + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); ++node_i) { + Ref node = state->nodes[node_i]; + + if (node->skin >= 0 && node->mesh >= 0) { + const GLTFSkinIndex skin_i = node->skin; + + Map::Element *mi_element = state->scene_nodes.find(node_i); + ERR_CONTINUE_MSG(mi_element == nullptr, vformat("Unable to find node %d", node_i)); + + MeshInstance *mi = Object::cast_to(mi_element->get()); + ERR_CONTINUE_MSG(mi == nullptr, vformat("Unable to cast node %d of type %s to MeshInstance", node_i, mi_element->get()->get_class_name())); + + const GLTFSkeletonIndex skel_i = state->skins.write[node->skin]->skeleton; + Ref gltf_skeleton = state->skeletons.write[skel_i]; + Skeleton *skeleton = gltf_skeleton->godot_skeleton; + ERR_CONTINUE_MSG(skeleton == nullptr, vformat("Unable to find Skeleton for node %d skin %d", node_i, skin_i)); + + mi->get_parent()->remove_child(mi); + skeleton->add_child(mi); + mi->set_owner(skeleton->get_owner()); + + mi->set_skin(state->skins.write[skin_i]->godot_skin); + mi->set_skeleton_path(mi->get_path_to(skeleton)); + mi->set_transform(Transform()); + } + } +} + +GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref state, GLTFAnimation::Track p_track, Ref p_animation, Transform p_bone_rest, int32_t p_track_i, GLTFNodeIndex p_node_i) { + Animation::InterpolationType interpolation = p_animation->track_get_interpolation_type(p_track_i); + + GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) { + gltf_interpolation = GLTFAnimation::INTERP_STEP; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) { + gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE; + } + Animation::TrackType track_type = p_animation->track_get_type(p_track_i); + int32_t key_count = p_animation->track_get_key_count(p_track_i); + Vector times; + times.resize(key_count); + String path = p_animation->track_get_path(p_track_i); + for (int32_t key_i = 0; key_i < key_count; key_i++) { + times.write[key_i] = p_animation->track_get_key_time(p_track_i, key_i); + } + const float BAKE_FPS = 30.0f; + if (track_type == Animation::TYPE_TRANSFORM) { + p_track.translation_track.times = times; + p_track.translation_track.interpolation = gltf_interpolation; + p_track.rotation_track.times = times; + p_track.rotation_track.interpolation = gltf_interpolation; + p_track.scale_track.times = times; + p_track.scale_track.interpolation = gltf_interpolation; + + p_track.scale_track.values.resize(key_count); + p_track.scale_track.interpolation = gltf_interpolation; + p_track.translation_track.values.resize(key_count); + p_track.translation_track.interpolation = gltf_interpolation; + p_track.rotation_track.values.resize(key_count); + p_track.rotation_track.interpolation = gltf_interpolation; + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 translation; + Quat rotation; + Vector3 scale; + Error err = p_animation->transform_track_get_key(p_track_i, key_i, &translation, &rotation, &scale); + ERR_CONTINUE(err != OK); + Transform xform; + xform.basis.set_quat_scale(rotation, scale); + xform.origin = translation; + xform = p_bone_rest * xform; + p_track.translation_track.values.write[key_i] = xform.get_origin(); + p_track.rotation_track.values.write[key_i] = xform.basis.get_rotation_quat(); + p_track.scale_track.values.write[key_i] = xform.basis.get_scale(); + } + } else if (path.find(":transform") != -1) { + p_track.translation_track.times = times; + p_track.translation_track.interpolation = gltf_interpolation; + p_track.rotation_track.times = times; + p_track.rotation_track.interpolation = gltf_interpolation; + p_track.scale_track.times = times; + p_track.scale_track.interpolation = gltf_interpolation; + + p_track.scale_track.values.resize(key_count); + p_track.scale_track.interpolation = gltf_interpolation; + p_track.translation_track.values.resize(key_count); + p_track.translation_track.interpolation = gltf_interpolation; + p_track.rotation_track.values.resize(key_count); + p_track.rotation_track.interpolation = gltf_interpolation; + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Transform xform = p_animation->track_get_key_value(p_track_i, key_i); + p_track.translation_track.values.write[key_i] = xform.get_origin(); + p_track.rotation_track.values.write[key_i] = xform.basis.get_rotation_quat(); + p_track.scale_track.values.write[key_i] = xform.basis.get_scale(); + } + } else if (track_type == Animation::TYPE_VALUE) { + if (path.find("/rotation_quat") != -1) { + p_track.rotation_track.times = times; + p_track.rotation_track.interpolation = gltf_interpolation; + + p_track.rotation_track.values.resize(key_count); + p_track.rotation_track.interpolation = gltf_interpolation; + + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Quat rotation_track = p_animation->track_get_key_value(p_track_i, key_i); + p_track.rotation_track.values.write[key_i] = rotation_track; + } + } else if (path.find(":translation") != -1) { + p_track.translation_track.times = times; + p_track.translation_track.interpolation = gltf_interpolation; + + p_track.translation_track.values.resize(key_count); + p_track.translation_track.interpolation = gltf_interpolation; + + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 translation = p_animation->track_get_key_value(p_track_i, key_i); + p_track.translation_track.values.write[key_i] = translation; + } + } else if (path.find(":rotation_degrees") != -1) { + p_track.rotation_track.times = times; + p_track.rotation_track.interpolation = gltf_interpolation; + + p_track.rotation_track.values.resize(key_count); + p_track.rotation_track.interpolation = gltf_interpolation; + + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 rotation_degrees = p_animation->track_get_key_value(p_track_i, key_i); + Vector3 rotation_radian; + rotation_radian.x = Math::deg2rad(rotation_degrees.x); + rotation_radian.y = Math::deg2rad(rotation_degrees.y); + rotation_radian.z = Math::deg2rad(rotation_degrees.z); + p_track.rotation_track.values.write[key_i] = Quat(rotation_radian); + } + } else if (path.find(":scale") != -1) { + p_track.scale_track.times = times; + p_track.scale_track.interpolation = gltf_interpolation; + + p_track.scale_track.values.resize(key_count); + p_track.scale_track.interpolation = gltf_interpolation; + + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i); + p_track.scale_track.values.write[key_i] = scale_track; + } + } + } else if (track_type == Animation::TYPE_BEZIER) { + if (path.find("/scale") != -1) { + const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; + if (!p_track.scale_track.times.size()) { + Vector new_times; + new_times.resize(keys); + for (int32_t key_i = 0; key_i < keys; key_i++) { + new_times.write[key_i] = key_i / BAKE_FPS; + } + p_track.scale_track.times = new_times; + p_track.scale_track.interpolation = gltf_interpolation; + + p_track.scale_track.values.resize(keys); + + for (int32_t key_i = 0; key_i < keys; key_i++) { + p_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f); + } + p_track.scale_track.interpolation = gltf_interpolation; + } + + for (int32_t key_i = 0; key_i < keys; key_i++) { + Vector3 bezier_track = p_track.scale_track.values[key_i]; + if (path.find("/scale:x") != -1) { + bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.x = p_bone_rest.affine_inverse().basis.get_scale().x * bezier_track.x; + } else if (path.find("/scale:y") != -1) { + bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.y = p_bone_rest.affine_inverse().basis.get_scale().y * bezier_track.y; + } else if (path.find("/scale:z") != -1) { + bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.z = p_bone_rest.affine_inverse().basis.get_scale().z * bezier_track.z; + } + p_track.scale_track.values.write[key_i] = bezier_track; + } + } else if (path.find("/translation") != -1) { + const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; + if (!p_track.translation_track.times.size()) { + Vector new_times; + new_times.resize(keys); + for (int32_t key_i = 0; key_i < keys; key_i++) { + new_times.write[key_i] = key_i / BAKE_FPS; + } + p_track.translation_track.times = new_times; + p_track.translation_track.interpolation = gltf_interpolation; + + p_track.translation_track.values.resize(keys); + p_track.translation_track.interpolation = gltf_interpolation; + } + + for (int32_t key_i = 0; key_i < keys; key_i++) { + Vector3 bezier_track = p_track.translation_track.values[key_i]; + if (path.find("/translation:x") != -1) { + bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.x = p_bone_rest.affine_inverse().origin.x * bezier_track.x; + } else if (path.find("/translation:y") != -1) { + bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.y = p_bone_rest.affine_inverse().origin.y * bezier_track.y; + } else if (path.find("/translation:z") != -1) { + bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.z = p_bone_rest.affine_inverse().origin.z * bezier_track.z; + } + p_track.translation_track.values.write[key_i] = bezier_track; + } + } + } + + return p_track; +} + +void GLTFDocument::_convert_animation(Ref state, AnimationPlayer *ap, String p_animation_track_name) { + Ref animation = ap->get_animation(p_animation_track_name); + Ref gltf_animation; + gltf_animation.instance(); + gltf_animation->set_name(_gen_unique_name(state, p_animation_track_name)); + + for (int32_t track_i = 0; track_i < animation->get_track_count(); track_i++) { + if (!animation->track_is_enabled(track_i)) { + continue; + } + String orig_track_path = animation->track_get_path(track_i); + if (String(orig_track_path).find(":translation") != -1) { + const Vector node_suffix = String(orig_track_path).split(":translation"); + const NodePath path = node_suffix[0]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map::Element *translation_scene_node_i = state->scene_nodes.front(); translation_scene_node_i; translation_scene_node_i = translation_scene_node_i->next()) { + if (translation_scene_node_i->get() == node) { + GLTFNodeIndex node_index = translation_scene_node_i->key(); + Map::Element *translation_track_i = gltf_animation->get_tracks().find(node_index); + GLTFAnimation::Track track; + if (translation_track_i) { + track = translation_track_i->get(); + } + track = _convert_animation_track(state, track, animation, Transform(), track_i, node_index); + gltf_animation->get_tracks().insert(node_index, track); + } + } + } else if (String(orig_track_path).find(":rotation_degrees") != -1) { + const Vector node_suffix = String(orig_track_path).split(":rotation_degrees"); + const NodePath path = node_suffix[0]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map::Element *rotation_degree_scene_node_i = state->scene_nodes.front(); rotation_degree_scene_node_i; rotation_degree_scene_node_i = rotation_degree_scene_node_i->next()) { + if (rotation_degree_scene_node_i->get() == node) { + GLTFNodeIndex node_index = rotation_degree_scene_node_i->key(); + Map::Element *rotation_degree_track_i = gltf_animation->get_tracks().find(node_index); + GLTFAnimation::Track track; + if (rotation_degree_track_i) { + track = rotation_degree_track_i->get(); + } + track = _convert_animation_track(state, track, animation, Transform(), track_i, node_index); + gltf_animation->get_tracks().insert(node_index, track); + } + } + } else if (String(orig_track_path).find(":scale") != -1) { + const Vector node_suffix = String(orig_track_path).split(":scale"); + const NodePath path = node_suffix[0]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map::Element *scale_scene_node_i = state->scene_nodes.front(); scale_scene_node_i; scale_scene_node_i = scale_scene_node_i->next()) { + if (scale_scene_node_i->get() == node) { + GLTFNodeIndex node_index = scale_scene_node_i->key(); + Map::Element *scale_track_i = gltf_animation->get_tracks().find(node_index); + GLTFAnimation::Track track; + if (scale_track_i) { + track = scale_track_i->get(); + } + track = _convert_animation_track(state, track, animation, Transform(), track_i, node_index); + gltf_animation->get_tracks().insert(node_index, track); + } + } + } else if (String(orig_track_path).find(":transform") != -1) { + const Vector node_suffix = String(orig_track_path).split(":transform"); + const NodePath path = node_suffix[0]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map::Element *transform_track_i = state->scene_nodes.front(); transform_track_i; transform_track_i = transform_track_i->next()) { + if (transform_track_i->get() == node) { + GLTFAnimation::Track track; + track = _convert_animation_track(state, track, animation, Transform(), track_i, transform_track_i->key()); + gltf_animation->get_tracks().insert(transform_track_i->key(), track); + } + } + } else if (String(orig_track_path).find(":blend_shapes/") != -1) { + const Vector node_suffix = String(orig_track_path).split(":blend_shapes/"); + const NodePath path = node_suffix[0]; + const String suffix = node_suffix[1]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map::Element *transform_track_i = state->scene_nodes.front(); transform_track_i; transform_track_i = transform_track_i->next()) { + if (transform_track_i->get() == node) { + const MeshInstance *mi = Object::cast_to(node); + if (!mi) { + continue; + } + Ref array_mesh = mi->get_mesh(); + if (array_mesh.is_null()) { + continue; + } + if (node_suffix.size() != 2) { + continue; + } + GLTFNodeIndex mesh_index = -1; + for (GLTFNodeIndex node_i = 0; node_i < state->scene_nodes.size(); node_i++) { + if (state->scene_nodes[node_i] == node) { + mesh_index = node_i; + break; + } + } + ERR_CONTINUE(mesh_index == -1); + Ref mesh = mi->get_mesh(); + ERR_CONTINUE(mesh.is_null()); + for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) { + if (mesh->get_blend_shape_name(shape_i) != suffix) { + continue; + } + GLTFAnimation::Track track; + Map::Element *blend_shape_track_i = gltf_animation->get_tracks().find(mesh_index); + if (blend_shape_track_i) { + track = blend_shape_track_i->get(); + } + Animation::InterpolationType interpolation = animation->track_get_interpolation_type(track_i); + + GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) { + gltf_interpolation = GLTFAnimation::INTERP_STEP; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) { + gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE; + } + Animation::TrackType track_type = animation->track_get_type(track_i); + if (track_type == Animation::TYPE_VALUE) { + int32_t key_count = animation->track_get_key_count(track_i); + GLTFAnimation::Channel weight; + weight.interpolation = gltf_interpolation; + weight.times.resize(key_count); + for (int32_t time_i = 0; time_i < key_count; time_i++) { + weight.times.write[time_i] = animation->track_get_key_time(track_i, time_i); + } + weight.values.resize(key_count); + for (int32_t value_i = 0; value_i < key_count; value_i++) { + weight.values.write[value_i] = animation->track_get_key_value(track_i, value_i); + } + track.weight_tracks.push_back(weight); + } + gltf_animation->get_tracks()[mesh_index] = track; + } + } + } + + } else if (String(orig_track_path).find(":") != -1) { + //Process skeleton + const Vector node_suffix = String(orig_track_path).split(":"); + const String node = node_suffix[0]; + const NodePath node_path = node; + const String suffix = node_suffix[1]; + Node *godot_node = ap->get_parent()->get_node_or_null(node_path); + Skeleton *skeleton = nullptr; + GLTFSkeletonIndex skeleton_gltf_i = -1; + for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) { + if (state->skeletons[skeleton_i]->godot_skeleton == cast_to(godot_node)) { + skeleton = state->skeletons[skeleton_i]->godot_skeleton; + skeleton_gltf_i = skeleton_i; + ERR_CONTINUE(!skeleton); + Ref skeleton_gltf = state->skeletons[skeleton_gltf_i]; + int32_t bone = skeleton->find_bone(suffix); + ERR_CONTINUE(bone == -1); + Transform xform = skeleton->get_bone_rest(bone); + if (!skeleton_gltf->godot_bone_node.has(bone)) { + continue; + } + GLTFNodeIndex node_i = skeleton_gltf->godot_bone_node[bone]; + Map::Element *property_track_i = gltf_animation->get_tracks().find(node_i); + GLTFAnimation::Track track; + if (property_track_i) { + track = property_track_i->get(); + } + track = _convert_animation_track(state, track, animation, xform, track_i, node_i); + gltf_animation->get_tracks()[node_i] = track; + } + } + } else if (String(orig_track_path).find(":") == -1) { + ERR_CONTINUE(!ap->get_parent()); + for (int32_t node_i = 0; node_i < ap->get_parent()->get_child_count(); node_i++) { + const Node *child = ap->get_parent()->get_child(node_i); + const Node *node = child->get_node_or_null(orig_track_path); + for (Map::Element *scene_node_i = state->scene_nodes.front(); scene_node_i; scene_node_i = scene_node_i->next()) { + if (scene_node_i->get() == node) { + GLTFNodeIndex node_index = scene_node_i->key(); + Map::Element *node_track_i = gltf_animation->get_tracks().find(node_index); + GLTFAnimation::Track track; + if (node_track_i) { + track = node_track_i->get(); + } + track = _convert_animation_track(state, track, animation, Transform(), track_i, node_index); + gltf_animation->get_tracks().insert(node_index, track); + break; + } + } + } + } + } + if (gltf_animation->get_tracks().size()) { + state->animations.push_back(gltf_animation); + } +} + +Error GLTFDocument::parse(Ref state, String p_path, bool p_read_binary) { + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (!f) { + return err; + } + uint32_t magic = f->get_32(); + if (magic == 0x46546C67) { + //binary file + //text file + err = _parse_glb(p_path, state); + if (err) { + return FAILED; + } + } else { + //text file + err = _parse_json(p_path, state); + if (err) { + return FAILED; + } + } + f->close(); + + // get file's name, use for scene name if none + state->filename = p_path.get_file().get_slice(".", 0); + + ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED); + + Dictionary asset = state->json["asset"]; + + ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED); + + String version = asset["version"]; + + state->major_version = version.get_slice(".", 0).to_int(); + state->minor_version = version.get_slice(".", 1).to_int(); + + /* STEP 0 PARSE SCENE */ + err = _parse_scenes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 1 PARSE NODES */ + err = _parse_nodes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 2 PARSE BUFFERS */ + err = _parse_buffers(state, p_path.get_base_dir()); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 3 PARSE BUFFER VIEWS */ + err = _parse_buffer_views(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 4 PARSE ACCESSORS */ + err = _parse_accessors(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 5 PARSE IMAGES */ + err = _parse_images(state, p_path.get_base_dir()); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 6 PARSE TEXTURES */ + err = _parse_textures(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 7 PARSE TEXTURES */ + err = _parse_materials(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 9 PARSE SKINS */ + err = _parse_skins(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 10 DETERMINE SKELETONS */ + err = _determine_skeletons(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 11 CREATE SKELETONS */ + err = _create_skeletons(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 12 CREATE SKINS */ + err = _create_skins(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 13 PARSE MESHES (we have enough info now) */ + err = _parse_meshes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 14 PARSE LIGHTS */ + err = _parse_lights(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 15 PARSE CAMERAS */ + err = _parse_cameras(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 16 PARSE ANIMATIONS */ + err = _parse_animations(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 17 ASSIGN SCENE NAMES */ + _assign_scene_names(state); + + return OK; +} + +Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref p_material) { + Dictionary extension; + Ref mat = p_material; + if (mat.is_valid()) { + Dictionary texture_transform; + Array offset; + offset.resize(2); + offset[0] = mat->get_uv2_offset().x; + offset[1] = mat->get_uv2_offset().y; + texture_transform["offset"] = offset; + Array scale; + scale.resize(2); + scale[0] = mat->get_uv2_scale().x; + scale[1] = mat->get_uv2_scale().y; + texture_transform["scale"] = scale; + // Godot doesn't support texture rotation + extension["KHR_texture_transform"] = texture_transform; + } + return extension; +} + +Dictionary GLTFDocument::_serialize_texture_transform_uv1(Ref p_material) { + Dictionary extension; + if (p_material.is_valid()) { + Dictionary texture_transform; + Array offset; + offset.resize(2); + offset[0] = p_material->get_uv1_offset().x; + offset[1] = p_material->get_uv1_offset().y; + texture_transform["offset"] = offset; + Array scale; + scale.resize(2); + scale[0] = p_material->get_uv1_scale().x; + scale[1] = p_material->get_uv1_scale().y; + texture_transform["scale"] = scale; + // Godot doesn't support texture rotation + extension["KHR_texture_transform"] = texture_transform; + } + return extension; +} + +Error GLTFDocument::_serialize_version(Ref state) { + const String version = "2.0"; + state->major_version = version.get_slice(".", 0).to_int(); + state->minor_version = version.get_slice(".", 1).to_int(); + Dictionary asset; + asset["version"] = version; + + String hash = VERSION_HASH; + asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.length() == 0 ? String("unknown") : hash); + state->json["asset"] = asset; + ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED); + ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED); + return OK; +} + +Error GLTFDocument::_serialize_file(Ref state, const String p_path) { + Error err = FAILED; + if (p_path.to_lower().ends_with("glb")) { + err = _encode_buffer_glb(state, p_path); + ERR_FAIL_COND_V(err != OK, err); + FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(!f, FAILED); + + String json = JSON::print(state->json); + + const uint32_t magic = 0x46546C67; // GLTF + const int32_t header_size = 12; + const int32_t chunk_header_size = 8; + + for (int32_t pad_i = 0; pad_i < (chunk_header_size + json.utf8().length()) % 4; pad_i++) { + json += " "; + } + CharString cs = json.utf8(); + const uint32_t text_chunk_length = cs.length(); + + const uint32_t text_chunk_type = 0x4E4F534A; //JSON + int32_t binary_data_length = 0; + if (state->buffers.size()) { + binary_data_length = state->buffers[0].size(); + } + const int32_t binary_chunk_length = binary_data_length; + const int32_t binary_chunk_type = 0x004E4942; //BIN + + f->create(FileAccess::ACCESS_RESOURCES); + f->store_32(magic); + f->store_32(state->major_version); // version + f->store_32(header_size + chunk_header_size + text_chunk_length + chunk_header_size + binary_data_length); // length + f->store_32(text_chunk_length); + f->store_32(text_chunk_type); + f->store_buffer((uint8_t *)&cs[0], cs.length()); + if (binary_chunk_length) { + f->store_32(binary_chunk_length); + f->store_32(binary_chunk_type); + f->store_buffer(state->buffers[0].ptr(), binary_data_length); + } + + f->close(); + } else { + err = _encode_buffer_bins(state, p_path); + ERR_FAIL_COND_V(err != OK, err); + FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(!f, FAILED); + + f->create(FileAccess::ACCESS_RESOURCES); + String json = JSON::print(state->json); + f->store_string(json); + f->close(); + } + return err; +} diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h new file mode 100644 index 0000000000..993d870be9 --- /dev/null +++ b/modules/gltf/gltf_document.h @@ -0,0 +1,435 @@ +/*************************************************************************/ +/* gltf_document.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_H +#define GLTF_DOCUMENT_H + +#include "editor/import/resource_importer_scene.h" +#include "gltf_animation.h" +#include "scene/2d/node_2d.h" +#include "scene/3d/bone_attachment.h" +#include "scene/3d/light.h" +#include "scene/3d/mesh_instance.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/spatial.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/material.h" +#include "scene/resources/texture.h" + +class GLTFState; +class GLTFSkin; +class GLTFNode; +class GLTFSpecGloss; +class GLTFSkeleton; + +using GLTFAccessorIndex = int; +using GLTFAnimationIndex = int; +using GLTFBufferIndex = int; +using GLTFBufferViewIndex = int; +using GLTFCameraIndex = int; +using GLTFImageIndex = int; +using GLTFMaterialIndex = int; +using GLTFMeshIndex = int; +using GLTFLightIndex = int; +using GLTFNodeIndex = int; +using GLTFSkeletonIndex = int; +using GLTFSkinIndex = int; +using GLTFTextureIndex = int; + +class GLTFDocument : public Resource { + GDCLASS(GLTFDocument, Resource); + friend class GLTFState; + friend class GLTFSkin; + friend class GLTFSkeleton; + +public: + const int32_t JOINT_GROUP_SIZE = 4; + enum GLTFType { + TYPE_SCALAR, + TYPE_VEC2, + TYPE_VEC3, + TYPE_VEC4, + TYPE_MAT2, + TYPE_MAT3, + TYPE_MAT4, + }; + + enum { + ARRAY_BUFFER = 34962, + ELEMENT_ARRAY_BUFFER = 34963, + + TYPE_BYTE = 5120, + TYPE_UNSIGNED_BYTE = 5121, + TYPE_SHORT = 5122, + TYPE_UNSIGNED_SHORT = 5123, + TYPE_UNSIGNED_INT = 5125, + TYPE_FLOAT = 5126, + + COMPONENT_TYPE_BYTE = 5120, + COMPONENT_TYPE_UNSIGNED_BYTE = 5121, + COMPONENT_TYPE_SHORT = 5122, + COMPONENT_TYPE_UNSIGNED_SHORT = 5123, + COMPONENT_TYPE_INT = 5125, + COMPONENT_TYPE_FLOAT = 5126, + }; + +private: + template + static Array to_array(const Vector &p_inp) { + Array ret; + for (int i = 0; i < p_inp.size(); i++) { + ret.push_back(p_inp[i]); + } + return ret; + } + + template + static Array to_array(const Set &p_inp) { + Array ret; + typename Set::Element *elem = p_inp.front(); + while (elem) { + ret.push_back(elem->get()); + elem = elem->next(); + } + return ret; + } + + template + static void set_from_array(Vector &r_out, const Array &p_inp) { + r_out.clear(); + for (int i = 0; i < p_inp.size(); i++) { + r_out.push_back(p_inp[i]); + } + } + + template + static void set_from_array(Set &r_out, const Array &p_inp) { + r_out.clear(); + for (int i = 0; i < p_inp.size(); i++) { + r_out.insert(p_inp[i]); + } + } + template + static Dictionary to_dict(const Map &p_inp) { + Dictionary ret; + for (typename Map::Element *E = p_inp.front(); E; E = E->next()) { + ret[E->key()] = E->value(); + } + return ret; + } + + template + static void set_from_dict(Map &r_out, const Dictionary &p_inp) { + r_out.clear(); + Array keys = p_inp.keys(); + for (int i = 0; i < keys.size(); i++) { + r_out[keys[i]] = p_inp[keys[i]]; + } + } + double _filter_number(double p_float); + String _get_component_type_name(const uint32_t p_component); + int _get_component_type_size(const int component_type); + Error _parse_scenes(Ref state); + Error _parse_nodes(Ref state); + String _get_type_name(const GLTFType p_component); + String _get_accessor_type_name(const GLTFDocument::GLTFType p_type); + String _gen_unique_name(Ref state, const String &p_name); + String _sanitize_animation_name(const String &name); + String _gen_unique_animation_name(Ref state, const String &p_name); + String _sanitize_bone_name(Ref state, const String &name); + String _gen_unique_bone_name(Ref state, + const GLTFSkeletonIndex skel_i, + const String &p_name); + GLTFTextureIndex _set_texture(Ref state, Ref p_texture); + Ref _get_texture(Ref state, + const GLTFTextureIndex p_texture); + Error _parse_json(const String &p_path, Ref state); + Error _parse_glb(const String &p_path, Ref state); + void _compute_node_heights(Ref state); + Error _parse_buffers(Ref state, const String &p_base_path); + Error _parse_buffer_views(Ref state); + GLTFType _get_type_from_str(const String &p_string); + Error _parse_accessors(Ref state); + Error _decode_buffer_view(Ref state, double *dst, + const GLTFBufferViewIndex p_buffer_view, + const int skip_every, const int skip_bytes, + const int element_size, const int count, + const GLTFType type, const int component_count, + const int component_type, const int component_size, + const bool normalized, const int byte_offset, + const bool for_vertex); + Vector _decode_accessor(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_floats(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_ints(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_vec2(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_vec3(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_color(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_quat(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_xform2d(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_basis(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector _decode_accessor_as_xform(Ref state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Error _parse_meshes(Ref state); + Error _serialize_textures(Ref state); + Error _serialize_images(Ref state, const String &p_path); + Error _serialize_lights(Ref state); + Error _parse_images(Ref state, const String &p_base_path); + Error _parse_textures(Ref state); + Error _parse_materials(Ref state); + void _set_texture_transform_uv1(const Dictionary &d, Ref material); + void spec_gloss_to_rough_metal(Ref r_spec_gloss, + Ref p_material); + static void spec_gloss_to_metal_base_color(const Color &p_specular_factor, + const Color &p_diffuse, + Color &r_base_color, + float &r_metallic); + GLTFNodeIndex _find_highest_node(Ref state, + const Vector &subset); + bool _capture_nodes_in_skin(Ref state, Ref skin, + const GLTFNodeIndex node_index); + void _capture_nodes_for_multirooted_skin(Ref state, Ref skin); + Error _expand_skin(Ref state, Ref skin); + Error _verify_skin(Ref state, Ref skin); + Error _parse_skins(Ref state); + Error _determine_skeletons(Ref state); + Error _reparent_non_joint_skeleton_subtrees( + Ref state, Ref skeleton, + const Vector &non_joints); + Error _reparent_to_fake_joint(Ref state, Ref skeleton, + const GLTFNodeIndex node_index); + Error _determine_skeleton_roots(Ref state, + const GLTFSkeletonIndex skel_i); + Error _create_skeletons(Ref state); + Error _map_skin_joints_indices_to_skeleton_bone_indices(Ref state); + Error _serialize_skins(Ref state); + Error _create_skins(Ref state); + bool _skins_are_same(const Ref skin_a, const Ref skin_b); + void _remove_duplicate_skins(Ref state); + Error _serialize_cameras(Ref state); + Error _parse_cameras(Ref state); + Error _parse_lights(Ref state); + Error _parse_animations(Ref state); + Error _serialize_animations(Ref state); + BoneAttachment *_generate_bone_attachment(Ref state, + Skeleton *skeleton, + const GLTFNodeIndex node_index); + Spatial *_generate_mesh_instance(Ref state, Node *scene_parent, const GLTFNodeIndex node_index); + Camera *_generate_camera(Ref state, Node *scene_parent, + const GLTFNodeIndex node_index); + Light *_generate_light(Ref state, Node *scene_parent, const GLTFNodeIndex node_index); + Spatial *_generate_spatial(Ref state, Node *scene_parent, + const GLTFNodeIndex node_index); + void _assign_scene_names(Ref state); + template + T _interpolate_track(const Vector &p_times, const Vector &p_values, + const float p_time, + const GLTFAnimation::Interpolation p_interp); + GLTFAccessorIndex _encode_accessor_as_quats(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_weights(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_joints(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_floats(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_vec2(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + + void _calc_accessor_vec2_min_max(int i, const int element_count, Vector &type_max, Vector2 attribs, Vector &type_min) { + if (i == 0) { + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = attribs[(i * element_count) + type_i]; + type_min.write[type_i] = attribs[(i * element_count) + type_i]; + } + } + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = MAX(attribs[(i * element_count) + type_i], type_max[type_i]); + type_min.write[type_i] = MIN(attribs[(i * element_count) + type_i], type_min[type_i]); + type_max.write[type_i] = _filter_number(type_max.write[type_i]); + type_min.write[type_i] = _filter_number(type_min.write[type_i]); + } + } + + GLTFAccessorIndex _encode_accessor_as_vec3(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_color(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + + void _calc_accessor_min_max(int p_i, const int p_element_count, Vector &p_type_max, Vector p_attribs, Vector &p_type_min); + + GLTFAccessorIndex _encode_accessor_as_ints(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_xform(Ref state, + const Vector p_attribs, + const bool p_for_vertex); + Error _encode_buffer_view(Ref state, const double *src, + const int count, const GLTFType type, + const int component_type, const bool normalized, + const int byte_offset, const bool for_vertex, + GLTFBufferViewIndex &r_accessor); + Error _encode_accessors(Ref state); + Error _encode_buffer_views(Ref state); + Error _serialize_materials(Ref state); + Error _serialize_meshes(Ref state); + Error _serialize_nodes(Ref state); + Error _serialize_scenes(Ref state); + String interpolation_to_string(const GLTFAnimation::Interpolation p_interp); + GLTFAnimation::Track _convert_animation_track(Ref state, + GLTFAnimation::Track p_track, + Ref p_animation, Transform p_bone_rest, + int32_t p_track_i, + GLTFNodeIndex p_node_i); + Error _encode_buffer_bins(Ref state, const String &p_path); + Error _encode_buffer_glb(Ref state, const String &p_path); + Error _serialize_bone_attachment(Ref state); + Dictionary _serialize_texture_transform_uv1(Ref p_material); + Dictionary _serialize_texture_transform_uv2(Ref p_material); + Error _serialize_version(Ref state); + Error _serialize_file(Ref state, const String p_path); + Error _serialize_extensions(Ref state) const; + +public: + // http://www.itu.int/rec/R-REC-BT.601 + // http://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf + static constexpr float R_BRIGHTNESS_COEFF = 0.299f; + static constexpr float G_BRIGHTNESS_COEFF = 0.587f; + static constexpr float B_BRIGHTNESS_COEFF = 0.114f; + +private: + // https://github.com/microsoft/glTF-SDK/blob/master/GLTFSDK/Source/PBRUtils.cpp#L9 + // https://bghgary.github.io/glTF/convert-between-workflows-bjs/js/babylon.pbrUtilities.js + static float solve_metallic(float p_dielectric_specular, float diffuse, + float specular, + float p_one_minus_specular_strength); + static float get_perceived_brightness(const Color p_color); + static float get_max_component(const Color &p_color); + +public: + String _sanitize_scene_name(Ref state, const String &p_name); + String _legacy_validate_node_name(const String &p_name); + + void _process_mesh_instances(Ref state, Node *scene_root); + void _generate_scene_node(Ref state, Node *scene_parent, + Spatial *scene_root, + const GLTFNodeIndex node_index); + void _import_animation(Ref state, AnimationPlayer *ap, + const GLTFAnimationIndex index, const int bake_fps); + GLTFMeshIndex _convert_mesh_instance(Ref state, + MeshInstance *p_mesh_instance); + void _convert_mesh_instances(Ref state); + GLTFCameraIndex _convert_camera(Ref state, Camera *p_camera); + void _convert_light_to_gltf(Light *light, Ref state, Spatial *spatial, Ref gltf_node); + GLTFLightIndex _convert_light(Ref state, Light *p_light); + GLTFSkeletonIndex _convert_skeleton(Ref state, Skeleton *p_skeleton); + void _convert_spatial(Ref state, Spatial *p_spatial, Ref p_node); + void _convert_scene_node(Ref state, Node *p_current, Node *p_root, + const GLTFNodeIndex p_gltf_current, + const GLTFNodeIndex p_gltf_root); + +#ifdef MODULE_CSG_ENABLED + void _convert_csg_shape_to_gltf(Node *p_current, GLTFNodeIndex p_gltf_parent, Ref gltf_node, Ref state); +#endif // MODULE_CSG_ENABLED + + void _create_gltf_node(Ref state, + Node *p_scene_parent, + GLTFNodeIndex current_node_i, + GLTFNodeIndex p_parent_node_index, + GLTFNodeIndex p_root_gltf_node, + Ref gltf_node); + void _convert_animation_player_to_gltf( + AnimationPlayer *animation_player, Ref state, + const GLTFNodeIndex &p_gltf_current, + const GLTFNodeIndex &p_gltf_root_index, + Ref p_gltf_node, Node *p_scene_parent, + Node *p_root); + void _check_visibility(Node *p_node, bool &retflag); + void _convert_camera_to_gltf(Camera *camera, Ref state, + Spatial *spatial, + Ref gltf_node); +#ifdef MODULE_GRIDMAP_ENABLED + void _convert_grid_map_to_gltf( + Node *p_scene_parent, + const GLTFNodeIndex &p_parent_node_index, + const GLTFNodeIndex &p_root_node_index, + Ref gltf_node, Ref state, + Node *p_root_node); +#endif // MODULE_GRIDMAP_ENABLED + void _convert_mult_mesh_instance_to_gltf( + Node *p_scene_parent, + const GLTFNodeIndex &p_parent_node_index, + const GLTFNodeIndex &p_root_node_index, + Ref gltf_node, Ref state, + Node *p_root_node); + void _convert_skeleton_to_gltf( + Node *p_scene_parent, Ref state, + const GLTFNodeIndex &p_parent_node_index, + const GLTFNodeIndex &p_root_node_index, + Ref gltf_node, Node *p_root_node); + void _convert_bone_attachment_to_gltf(Node *p_scene_parent, + Ref state, + Ref gltf_node, + bool &retflag); + void _convert_mesh_to_gltf(Node *p_scene_parent, + Ref state, Spatial *spatial, + Ref gltf_node); + void _convert_animation(Ref state, AnimationPlayer *ap, + String p_animation_track_name); + Error serialize(Ref state, Node *p_root, const String &p_path); + Error parse(Ref state, String p_paths, bool p_read_binary = false); +}; + +#endif // GLTF_DOCUMENT_H diff --git a/modules/gltf/gltf_light.cpp b/modules/gltf/gltf_light.cpp new file mode 100644 index 0000000000..b54026cb40 --- /dev/null +++ b/modules/gltf/gltf_light.cpp @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* gltf_light.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_light.h" + +void GLTFLight::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_color"), &GLTFLight::get_color); + ClassDB::bind_method(D_METHOD("set_color", "color"), &GLTFLight::set_color); + ClassDB::bind_method(D_METHOD("get_intensity"), &GLTFLight::get_intensity); + ClassDB::bind_method(D_METHOD("set_intensity", "intensity"), &GLTFLight::set_intensity); + ClassDB::bind_method(D_METHOD("get_type"), &GLTFLight::get_type); + ClassDB::bind_method(D_METHOD("set_type", "type"), &GLTFLight::set_type); + ClassDB::bind_method(D_METHOD("get_range"), &GLTFLight::get_range); + ClassDB::bind_method(D_METHOD("set_range", "range"), &GLTFLight::set_range); + ClassDB::bind_method(D_METHOD("get_inner_cone_angle"), &GLTFLight::get_inner_cone_angle); + ClassDB::bind_method(D_METHOD("set_inner_cone_angle", "inner_cone_angle"), &GLTFLight::set_inner_cone_angle); + ClassDB::bind_method(D_METHOD("get_outer_cone_angle"), &GLTFLight::get_outer_cone_angle); + ClassDB::bind_method(D_METHOD("set_outer_cone_angle", "outer_cone_angle"), &GLTFLight::set_outer_cone_angle); + + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); // Color + ADD_PROPERTY(PropertyInfo(Variant::REAL, "intensity"), "set_intensity", "get_intensity"); // float + ADD_PROPERTY(PropertyInfo(Variant::STRING, "type"), "set_type", "get_type"); // String + ADD_PROPERTY(PropertyInfo(Variant::REAL, "range"), "set_range", "get_range"); // float + ADD_PROPERTY(PropertyInfo(Variant::REAL, "inner_cone_angle"), "set_inner_cone_angle", "get_inner_cone_angle"); // float + ADD_PROPERTY(PropertyInfo(Variant::REAL, "outer_cone_angle"), "set_outer_cone_angle", "get_outer_cone_angle"); // float +} + +Color GLTFLight::get_color() { + return color; +} + +void GLTFLight::set_color(Color p_color) { + color = p_color; +} + +float GLTFLight::get_intensity() { + return intensity; +} + +void GLTFLight::set_intensity(float p_intensity) { + intensity = p_intensity; +} + +String GLTFLight::get_type() { + return type; +} + +void GLTFLight::set_type(String p_type) { + type = p_type; +} + +float GLTFLight::get_range() { + return range; +} + +void GLTFLight::set_range(float p_range) { + range = p_range; +} + +float GLTFLight::get_inner_cone_angle() { + return inner_cone_angle; +} + +void GLTFLight::set_inner_cone_angle(float p_inner_cone_angle) { + inner_cone_angle = p_inner_cone_angle; +} + +float GLTFLight::get_outer_cone_angle() { + return outer_cone_angle; +} + +void GLTFLight::set_outer_cone_angle(float p_outer_cone_angle) { + outer_cone_angle = p_outer_cone_angle; +} diff --git a/modules/gltf/gltf_light.h b/modules/gltf/gltf_light.h new file mode 100644 index 0000000000..b9702ecdbf --- /dev/null +++ b/modules/gltf/gltf_light.h @@ -0,0 +1,72 @@ +/*************************************************************************/ +/* gltf_light.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_LIGHT_H +#define GLTF_LIGHT_H + +#include "core/engine.h" +#include "core/resource.h" + +class GLTFLight : public Resource { + GDCLASS(GLTFLight, Resource) + friend class GLTFDocument; + +protected: + static void _bind_methods(); + +private: + Color color; + float intensity = 0.0f; + String type; + float range = 0.0f; + float inner_cone_angle = 0.0f; + float outer_cone_angle = 0.0f; + +public: + Color get_color(); + void set_color(Color p_color); + + float get_intensity(); + void set_intensity(float p_intensity); + + String get_type(); + void set_type(String p_type); + + float get_range(); + void set_range(float p_range); + + float get_inner_cone_angle(); + void set_inner_cone_angle(float p_inner_cone_angle); + + float get_outer_cone_angle(); + void set_outer_cone_angle(float p_outer_cone_angle); +}; + +#endif // GLTF_LIGHT_H diff --git a/modules/gltf/gltf_mesh.cpp b/modules/gltf/gltf_mesh.cpp new file mode 100644 index 0000000000..0628f2bec9 --- /dev/null +++ b/modules/gltf/gltf_mesh.cpp @@ -0,0 +1,57 @@ +/*************************************************************************/ +/* gltf_mesh.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_mesh.h" + +void GLTFMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_mesh"), &GLTFMesh::get_mesh); + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &GLTFMesh::set_mesh); + ClassDB::bind_method(D_METHOD("get_blend_weights"), &GLTFMesh::get_blend_weights); + ClassDB::bind_method(D_METHOD("set_blend_weights", "blend_weights"), &GLTFMesh::set_blend_weights); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh"), "set_mesh", "get_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_REAL_ARRAY, "blend_weights"), "set_blend_weights", "get_blend_weights"); // Vector +} + +Ref GLTFMesh::get_mesh() { + return mesh; +} + +void GLTFMesh::set_mesh(Ref p_mesh) { + mesh = p_mesh; +} + +Vector GLTFMesh::get_blend_weights() { + return blend_weights; +} + +void GLTFMesh::set_blend_weights(Vector p_blend_weights) { + blend_weights = p_blend_weights; +} diff --git a/modules/gltf/gltf_mesh.h b/modules/gltf/gltf_mesh.h new file mode 100644 index 0000000000..5f9a2f6928 --- /dev/null +++ b/modules/gltf/gltf_mesh.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* gltf_mesh.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_MESH_H +#define GLTF_MESH_H + +#include "core/resource.h" +#include "editor/import/resource_importer_scene.h" +#include "scene/resources/mesh.h" + +class GLTFMesh : public Resource { + GDCLASS(GLTFMesh, Resource); + +private: + Ref mesh; + Vector blend_weights; + +protected: + static void _bind_methods(); + +public: + Ref get_mesh(); + void set_mesh(Ref p_mesh); + Vector get_blend_weights(); + void set_blend_weights(Vector p_blend_weights); +}; +#endif // GLTF_MESH_H diff --git a/modules/gltf/gltf_node.cpp b/modules/gltf/gltf_node.cpp new file mode 100644 index 0000000000..0ac0b06510 --- /dev/null +++ b/modules/gltf/gltf_node.cpp @@ -0,0 +1,189 @@ +/*************************************************************************/ +/* gltf_node.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_node.h" + +void GLTFNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_parent"), &GLTFNode::get_parent); + ClassDB::bind_method(D_METHOD("set_parent", "parent"), &GLTFNode::set_parent); + ClassDB::bind_method(D_METHOD("get_height"), &GLTFNode::get_height); + ClassDB::bind_method(D_METHOD("set_height", "height"), &GLTFNode::set_height); + ClassDB::bind_method(D_METHOD("get_xform"), &GLTFNode::get_xform); + ClassDB::bind_method(D_METHOD("set_xform", "xform"), &GLTFNode::set_xform); + ClassDB::bind_method(D_METHOD("get_mesh"), &GLTFNode::get_mesh); + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &GLTFNode::set_mesh); + ClassDB::bind_method(D_METHOD("get_camera"), &GLTFNode::get_camera); + ClassDB::bind_method(D_METHOD("set_camera", "camera"), &GLTFNode::set_camera); + ClassDB::bind_method(D_METHOD("get_skin"), &GLTFNode::get_skin); + ClassDB::bind_method(D_METHOD("set_skin", "skin"), &GLTFNode::set_skin); + ClassDB::bind_method(D_METHOD("get_skeleton"), &GLTFNode::get_skeleton); + ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &GLTFNode::set_skeleton); + ClassDB::bind_method(D_METHOD("get_joint"), &GLTFNode::get_joint); + ClassDB::bind_method(D_METHOD("set_joint", "joint"), &GLTFNode::set_joint); + ClassDB::bind_method(D_METHOD("get_translation"), &GLTFNode::get_translation); + ClassDB::bind_method(D_METHOD("set_translation", "translation"), &GLTFNode::set_translation); + ClassDB::bind_method(D_METHOD("get_rotation"), &GLTFNode::get_rotation); + ClassDB::bind_method(D_METHOD("set_rotation", "rotation"), &GLTFNode::set_rotation); + ClassDB::bind_method(D_METHOD("get_scale"), &GLTFNode::get_scale); + ClassDB::bind_method(D_METHOD("set_scale", "scale"), &GLTFNode::set_scale); + ClassDB::bind_method(D_METHOD("get_children"), &GLTFNode::get_children); + ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children); + ClassDB::bind_method(D_METHOD("get_fake_joint_parent"), &GLTFNode::get_fake_joint_parent); + ClassDB::bind_method(D_METHOD("set_fake_joint_parent", "fake_joint_parent"), &GLTFNode::set_fake_joint_parent); + ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light); + ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height"); // int + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "xform"), "set_xform", "get_xform"); // Transform + ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh"), "set_mesh", "get_mesh"); // GLTFMeshIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "camera"), "set_camera", "get_camera"); // GLTFCameraIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "skin"), "set_skin", "get_skin"); // GLTFSkinIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton"), "set_skeleton", "get_skeleton"); // GLTFSkeletonIndex + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "joint"), "set_joint", "get_joint"); // bool + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "translation"), "set_translation", "get_translation"); // Vector3 + ADD_PROPERTY(PropertyInfo(Variant::QUAT, "rotation"), "set_rotation", "get_rotation"); // Quat + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); // Vector3 + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "children"), "set_children", "get_children"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::INT, "fake_joint_parent"), "set_fake_joint_parent", "get_fake_joint_parent"); // GLTFNodeIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "light"), "set_light", "get_light"); // GLTFLightIndex +} + +GLTFNodeIndex GLTFNode::get_parent() { + return parent; +} + +void GLTFNode::set_parent(GLTFNodeIndex p_parent) { + parent = p_parent; +} + +int GLTFNode::get_height() { + return height; +} + +void GLTFNode::set_height(int p_height) { + height = p_height; +} + +Transform GLTFNode::get_xform() { + return xform; +} + +void GLTFNode::set_xform(Transform p_xform) { + xform = p_xform; +} + +GLTFMeshIndex GLTFNode::get_mesh() { + return mesh; +} + +void GLTFNode::set_mesh(GLTFMeshIndex p_mesh) { + mesh = p_mesh; +} + +GLTFCameraIndex GLTFNode::get_camera() { + return camera; +} + +void GLTFNode::set_camera(GLTFCameraIndex p_camera) { + camera = p_camera; +} + +GLTFSkinIndex GLTFNode::get_skin() { + return skin; +} + +void GLTFNode::set_skin(GLTFSkinIndex p_skin) { + skin = p_skin; +} + +GLTFSkeletonIndex GLTFNode::get_skeleton() { + return skeleton; +} + +void GLTFNode::set_skeleton(GLTFSkeletonIndex p_skeleton) { + skeleton = p_skeleton; +} + +bool GLTFNode::get_joint() { + return joint; +} + +void GLTFNode::set_joint(bool p_joint) { + joint = p_joint; +} + +Vector3 GLTFNode::get_translation() { + return translation; +} + +void GLTFNode::set_translation(Vector3 p_translation) { + translation = p_translation; +} + +Quat GLTFNode::get_rotation() { + return rotation; +} + +void GLTFNode::set_rotation(Quat p_rotation) { + rotation = p_rotation; +} + +Vector3 GLTFNode::get_scale() { + return scale; +} + +void GLTFNode::set_scale(Vector3 p_scale) { + scale = p_scale; +} + +Vector GLTFNode::get_children() { + return children; +} + +void GLTFNode::set_children(Vector p_children) { + children = p_children; +} + +GLTFNodeIndex GLTFNode::get_fake_joint_parent() { + return fake_joint_parent; +} + +void GLTFNode::set_fake_joint_parent(GLTFNodeIndex p_fake_joint_parent) { + fake_joint_parent = p_fake_joint_parent; +} + +GLTFLightIndex GLTFNode::get_light() { + return light; +} + +void GLTFNode::set_light(GLTFLightIndex p_light) { + light = p_light; +} diff --git a/modules/gltf/gltf_node.h b/modules/gltf/gltf_node.h new file mode 100644 index 0000000000..32e41d037d --- /dev/null +++ b/modules/gltf/gltf_node.h @@ -0,0 +1,105 @@ +/*************************************************************************/ +/* gltf_node.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_NODE_H +#define GLTF_NODE_H + +#include "core/resource.h" +#include "gltf_document.h" + +class GLTFNode : public Resource { + GDCLASS(GLTFNode, Resource); + friend class GLTFDocument; + friend class PackedSceneGLTF; + +private: + // matrices need to be transformed to this + GLTFNodeIndex parent = -1; + int height = -1; + Transform xform; + GLTFMeshIndex mesh = -1; + GLTFCameraIndex camera = -1; + GLTFSkinIndex skin = -1; + GLTFSkeletonIndex skeleton = -1; + bool joint = false; + Vector3 translation; + Quat rotation; + Vector3 scale = Vector3(1, 1, 1); + Vector children; + GLTFNodeIndex fake_joint_parent = -1; + GLTFLightIndex light = -1; + +protected: + static void _bind_methods(); + +public: + GLTFNodeIndex get_parent(); + void set_parent(GLTFNodeIndex p_parent); + + int get_height(); + void set_height(int p_height); + + Transform get_xform(); + void set_xform(Transform p_xform); + + GLTFMeshIndex get_mesh(); + void set_mesh(GLTFMeshIndex p_mesh); + + GLTFCameraIndex get_camera(); + void set_camera(GLTFCameraIndex p_camera); + + GLTFSkinIndex get_skin(); + void set_skin(GLTFSkinIndex p_skin); + + GLTFSkeletonIndex get_skeleton(); + void set_skeleton(GLTFSkeletonIndex p_skeleton); + + bool get_joint(); + void set_joint(bool p_joint); + + Vector3 get_translation(); + void set_translation(Vector3 p_translation); + + Quat get_rotation(); + void set_rotation(Quat p_rotation); + + Vector3 get_scale(); + void set_scale(Vector3 p_scale); + + Vector get_children(); + void set_children(Vector p_children); + + GLTFNodeIndex get_fake_joint_parent(); + void set_fake_joint_parent(GLTFNodeIndex p_fake_joint_parent); + + GLTFLightIndex get_light(); + void set_light(GLTFLightIndex p_light); +}; +#endif // GLTF_NODE_H diff --git a/modules/gltf/gltf_skeleton.cpp b/modules/gltf/gltf_skeleton.cpp new file mode 100644 index 0000000000..aad490da4e --- /dev/null +++ b/modules/gltf/gltf_skeleton.cpp @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* gltf_skeleton.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_skeleton.h" + +void GLTFSkeleton::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_joints"), &GLTFSkeleton::get_joints); + ClassDB::bind_method(D_METHOD("set_joints", "joints"), &GLTFSkeleton::set_joints); + ClassDB::bind_method(D_METHOD("get_roots"), &GLTFSkeleton::get_roots); + ClassDB::bind_method(D_METHOD("set_roots", "roots"), &GLTFSkeleton::set_roots); + ClassDB::bind_method(D_METHOD("get_godot_skeleton"), &GLTFSkeleton::get_godot_skeleton); + ClassDB::bind_method(D_METHOD("get_unique_names"), &GLTFSkeleton::get_unique_names); + ClassDB::bind_method(D_METHOD("set_unique_names", "unique_names"), &GLTFSkeleton::set_unique_names); + ClassDB::bind_method(D_METHOD("get_godot_bone_node"), &GLTFSkeleton::get_godot_bone_node); + ClassDB::bind_method(D_METHOD("set_godot_bone_node", "godot_bone_node"), &GLTFSkeleton::set_godot_bone_node); + ClassDB::bind_method(D_METHOD("get_bone_attachment_count"), &GLTFSkeleton::get_bone_attachment_count); + ClassDB::bind_method(D_METHOD("get_bone_attachment", "idx"), &GLTFSkeleton::get_bone_attachment); + + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "joints"), "set_joints", "get_joints"); // PoolVector + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "roots"), "set_roots", "get_roots"); // PoolVector + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "unique_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_unique_names", "get_unique_names"); // Set + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "godot_bone_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_godot_bone_node", "get_godot_bone_node"); // Map GLTFSkeleton::get_joints() { + return joints; +} + +void GLTFSkeleton::set_joints(PoolVector p_joints) { + joints = p_joints; +} + +PoolVector GLTFSkeleton::get_roots() { + return roots; +} + +void GLTFSkeleton::set_roots(PoolVector p_roots) { + roots = p_roots; +} + +Skeleton *GLTFSkeleton::get_godot_skeleton() { + return godot_skeleton; +} + +Array GLTFSkeleton::get_unique_names() { + return GLTFDocument::to_array(unique_names); +} + +void GLTFSkeleton::set_unique_names(Array p_unique_names) { + GLTFDocument::set_from_array(unique_names, p_unique_names); +} + +Dictionary GLTFSkeleton::get_godot_bone_node() { + return GLTFDocument::to_dict(godot_bone_node); +} + +void GLTFSkeleton::set_godot_bone_node(Dictionary p_indict) { + GLTFDocument::set_from_dict(godot_bone_node, p_indict); +} + +BoneAttachment *GLTFSkeleton::get_bone_attachment(int idx) { + ERR_FAIL_INDEX_V(idx, bone_attachments.size(), nullptr); + return bone_attachments[idx]; +} + +int32_t GLTFSkeleton::get_bone_attachment_count() { + return bone_attachments.size(); +} diff --git a/modules/gltf/gltf_skeleton.h b/modules/gltf/gltf_skeleton.h new file mode 100644 index 0000000000..1f5c49ba1f --- /dev/null +++ b/modules/gltf/gltf_skeleton.h @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* gltf_skeleton.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_SKELETON_H +#define GLTF_SKELETON_H + +#include "core/resource.h" +#include "gltf_document.h" + +class GLTFSkeleton : public Resource { + GDCLASS(GLTFSkeleton, Resource); + friend class GLTFDocument; + +private: + // The *synthesized* skeletons joints + PoolVector joints; + + // The roots of the skeleton. If there are multiple, each root must have the + // same parent (ie roots are siblings) + PoolVector roots; + + // The created Skeleton for the scene + Skeleton *godot_skeleton = nullptr; + + // Set of unique bone names for the skeleton + Set unique_names; + + Map godot_bone_node; + + PoolVector bone_attachments; + +protected: + static void _bind_methods(); + +public: + PoolVector get_joints(); + void set_joints(PoolVector p_joints); + + PoolVector get_roots(); + void set_roots(PoolVector p_roots); + + Skeleton *get_godot_skeleton(); + + // Skeleton *get_godot_skeleton() { + // return this->godot_skeleton; + // } + // void set_godot_skeleton(Skeleton p_*godot_skeleton) { + // this->godot_skeleton = p_godot_skeleton; + // } + + Array get_unique_names(); + void set_unique_names(Array p_unique_names); + + //Map get_godot_bone_node() { + // return this->godot_bone_node; + //} + //void set_godot_bone_node(Map p_godot_bone_node) { + // this->godot_bone_node = p_godot_bone_node; + //} + Dictionary get_godot_bone_node(); + void set_godot_bone_node(Dictionary p_indict); + + //Dictionary get_godot_bone_node() { + // return VariantConversion::to_dict(this->godot_bone_node); + //} + //void set_godot_bone_node(Dictionary p_indict) { + // VariantConversion::set_from_dict(this->godot_bone_node, p_indict); + //} + + BoneAttachment *get_bone_attachment(int idx); + + int32_t get_bone_attachment_count(); +}; +#endif // GLTF_SKELETON_H diff --git a/modules/gltf/gltf_skin.cpp b/modules/gltf/gltf_skin.cpp new file mode 100644 index 0000000000..a740160042 --- /dev/null +++ b/modules/gltf/gltf_skin.cpp @@ -0,0 +1,155 @@ +/*************************************************************************/ +/* gltf_skin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_skin.h" + +void GLTFSkin::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_skin_root"), &GLTFSkin::get_skin_root); + ClassDB::bind_method(D_METHOD("set_skin_root", "skin_root"), &GLTFSkin::set_skin_root); + ClassDB::bind_method(D_METHOD("get_joints_original"), &GLTFSkin::get_joints_original); + ClassDB::bind_method(D_METHOD("set_joints_original", "joints_original"), &GLTFSkin::set_joints_original); + ClassDB::bind_method(D_METHOD("get_inverse_binds"), &GLTFSkin::get_inverse_binds); + ClassDB::bind_method(D_METHOD("set_inverse_binds", "inverse_binds"), &GLTFSkin::set_inverse_binds); + ClassDB::bind_method(D_METHOD("get_joints"), &GLTFSkin::get_joints); + ClassDB::bind_method(D_METHOD("set_joints", "joints"), &GLTFSkin::set_joints); + ClassDB::bind_method(D_METHOD("get_non_joints"), &GLTFSkin::get_non_joints); + ClassDB::bind_method(D_METHOD("set_non_joints", "non_joints"), &GLTFSkin::set_non_joints); + ClassDB::bind_method(D_METHOD("get_roots"), &GLTFSkin::get_roots); + ClassDB::bind_method(D_METHOD("set_roots", "roots"), &GLTFSkin::set_roots); + ClassDB::bind_method(D_METHOD("get_skeleton"), &GLTFSkin::get_skeleton); + ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &GLTFSkin::set_skeleton); + ClassDB::bind_method(D_METHOD("get_joint_i_to_bone_i"), &GLTFSkin::get_joint_i_to_bone_i); + ClassDB::bind_method(D_METHOD("set_joint_i_to_bone_i", "joint_i_to_bone_i"), &GLTFSkin::set_joint_i_to_bone_i); + ClassDB::bind_method(D_METHOD("get_joint_i_to_name"), &GLTFSkin::get_joint_i_to_name); + ClassDB::bind_method(D_METHOD("set_joint_i_to_name", "joint_i_to_name"), &GLTFSkin::set_joint_i_to_name); + ClassDB::bind_method(D_METHOD("get_godot_skin"), &GLTFSkin::get_godot_skin); + ClassDB::bind_method(D_METHOD("set_godot_skin", "godot_skin"), &GLTFSkin::set_godot_skin); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "skin_root"), "set_skin_root", "get_skin_root"); // GLTFNodeIndex + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "joints_original"), "set_joints_original", "get_joints_original"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "inverse_binds", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "set_inverse_binds", "get_inverse_binds"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "joints"), "set_joints", "get_joints"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "non_joints"), "set_non_joints", "get_non_joints"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "roots"), "set_roots", "get_roots"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton"), "set_skeleton", "get_skeleton"); // int + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "joint_i_to_bone_i", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "set_joint_i_to_bone_i", "get_joint_i_to_bone_i"); // Map +} + +GLTFNodeIndex GLTFSkin::get_skin_root() { + return skin_root; +} + +void GLTFSkin::set_skin_root(GLTFNodeIndex p_skin_root) { + skin_root = p_skin_root; +} + +Vector GLTFSkin::get_joints_original() { + return joints_original; +} + +void GLTFSkin::set_joints_original(Vector p_joints_original) { + joints_original = p_joints_original; +} + +Array GLTFSkin::get_inverse_binds() { + return GLTFDocument::to_array(inverse_binds); +} + +void GLTFSkin::set_inverse_binds(Array p_inverse_binds) { + GLTFDocument::set_from_array(inverse_binds, p_inverse_binds); +} + +Vector GLTFSkin::get_joints() { + return joints; +} + +void GLTFSkin::set_joints(Vector p_joints) { + joints = p_joints; +} + +Vector GLTFSkin::get_non_joints() { + return non_joints; +} + +void GLTFSkin::set_non_joints(Vector p_non_joints) { + non_joints = p_non_joints; +} + +Vector GLTFSkin::get_roots() { + return roots; +} + +void GLTFSkin::set_roots(Vector p_roots) { + roots = p_roots; +} + +int GLTFSkin::get_skeleton() { + return skeleton; +} + +void GLTFSkin::set_skeleton(int p_skeleton) { + skeleton = p_skeleton; +} + +Dictionary GLTFSkin::get_joint_i_to_bone_i() { + return GLTFDocument::to_dict(joint_i_to_bone_i); +} + +void GLTFSkin::set_joint_i_to_bone_i(Dictionary p_joint_i_to_bone_i) { + GLTFDocument::set_from_dict(joint_i_to_bone_i, p_joint_i_to_bone_i); +} + +Dictionary GLTFSkin::get_joint_i_to_name() { + Dictionary ret; + Map::Element *elem = joint_i_to_name.front(); + while (elem) { + ret[elem->key()] = String(elem->value()); + elem = elem->next(); + } + return ret; +} + +void GLTFSkin::set_joint_i_to_name(Dictionary p_joint_i_to_name) { + joint_i_to_name = Map(); + Array keys = p_joint_i_to_name.keys(); + for (int i = 0; i < keys.size(); i++) { + joint_i_to_name[keys[i]] = p_joint_i_to_name[keys[i]]; + } +} + +Ref GLTFSkin::get_godot_skin() { + return godot_skin; +} + +void GLTFSkin::set_godot_skin(Ref p_godot_skin) { + godot_skin = p_godot_skin; +} diff --git a/modules/gltf/gltf_skin.h b/modules/gltf/gltf_skin.h new file mode 100644 index 0000000000..1d8100723f --- /dev/null +++ b/modules/gltf/gltf_skin.h @@ -0,0 +1,109 @@ +/*************************************************************************/ +/* gltf_skin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_SKIN_H +#define GLTF_SKIN_H + +#include "core/resource.h" +#include "gltf_document.h" + +class GLTFSkin : public Resource { + GDCLASS(GLTFSkin, Resource); + friend class GLTFDocument; + +private: + // The "skeleton" property defined in the gltf spec. -1 = Scene Root + GLTFNodeIndex skin_root = -1; + + Vector joints_original; + Vector inverse_binds; + + // Note: joints + non_joints should form a complete subtree, or subtrees + // with a common parent + + // All nodes that are skins that are caught in-between the original joints + // (inclusive of joints_original) + Vector joints; + + // All Nodes that are caught in-between skin joint nodes, and are not + // defined as joints by any skin + Vector non_joints; + + // The roots of the skin. In the case of multiple roots, their parent *must* + // be the same (the roots must be siblings) + Vector roots; + + // The GLTF Skeleton this Skin points to (after we determine skeletons) + GLTFSkeletonIndex skeleton = -1; + + // A mapping from the joint indices (in the order of joints_original) to the + // Godot Skeleton's bone_indices + Map joint_i_to_bone_i; + Map joint_i_to_name; + + // The Actual Skin that will be created as a mapping between the IBM's of + // this skin to the generated skeleton for the mesh instances. + Ref godot_skin; + +protected: + static void _bind_methods(); + +public: + GLTFNodeIndex get_skin_root(); + void set_skin_root(GLTFNodeIndex p_skin_root); + + Vector get_joints_original(); + void set_joints_original(Vector p_joints_original); + + Array get_inverse_binds(); + void set_inverse_binds(Array p_inverse_binds); + + Vector get_joints(); + void set_joints(Vector p_joints); + + Vector get_non_joints(); + void set_non_joints(Vector p_non_joints); + + Vector get_roots(); + void set_roots(Vector p_roots); + + int get_skeleton(); + void set_skeleton(int p_skeleton); + + Dictionary get_joint_i_to_bone_i(); + void set_joint_i_to_bone_i(Dictionary p_joint_i_to_bone_i); + + Dictionary get_joint_i_to_name(); + void set_joint_i_to_name(Dictionary p_joint_i_to_name); + + Ref get_godot_skin(); + void set_godot_skin(Ref p_godot_skin); +}; +#endif // GLTF_SKIN_H diff --git a/modules/gltf/gltf_spec_gloss.cpp b/modules/gltf/gltf_spec_gloss.cpp new file mode 100644 index 0000000000..1a7e409ab1 --- /dev/null +++ b/modules/gltf/gltf_spec_gloss.cpp @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* gltf_spec_gloss.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_spec_gloss.h" + +void GLTFSpecGloss::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_diffuse_img"), &GLTFSpecGloss::get_diffuse_img); + ClassDB::bind_method(D_METHOD("set_diffuse_img", "diffuse_img"), &GLTFSpecGloss::set_diffuse_img); + ClassDB::bind_method(D_METHOD("get_diffuse_factor"), &GLTFSpecGloss::get_diffuse_factor); + ClassDB::bind_method(D_METHOD("set_diffuse_factor", "diffuse_factor"), &GLTFSpecGloss::set_diffuse_factor); + ClassDB::bind_method(D_METHOD("get_gloss_factor"), &GLTFSpecGloss::get_gloss_factor); + ClassDB::bind_method(D_METHOD("set_gloss_factor", "gloss_factor"), &GLTFSpecGloss::set_gloss_factor); + ClassDB::bind_method(D_METHOD("get_specular_factor"), &GLTFSpecGloss::get_specular_factor); + ClassDB::bind_method(D_METHOD("set_specular_factor", "specular_factor"), &GLTFSpecGloss::set_specular_factor); + ClassDB::bind_method(D_METHOD("get_spec_gloss_img"), &GLTFSpecGloss::get_spec_gloss_img); + ClassDB::bind_method(D_METHOD("set_spec_gloss_img", "spec_gloss_img"), &GLTFSpecGloss::set_spec_gloss_img); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "diffuse_img"), "set_diffuse_img", "get_diffuse_img"); // Ref + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "diffuse_factor"), "set_diffuse_factor", "get_diffuse_factor"); // Color + ADD_PROPERTY(PropertyInfo(Variant::REAL, "gloss_factor"), "set_gloss_factor", "get_gloss_factor"); // float + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "specular_factor"), "set_specular_factor", "get_specular_factor"); // Color + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "spec_gloss_img"), "set_spec_gloss_img", "get_spec_gloss_img"); // Ref +} + +Ref GLTFSpecGloss::get_diffuse_img() { + return diffuse_img; +} + +void GLTFSpecGloss::set_diffuse_img(Ref p_diffuse_img) { + diffuse_img = p_diffuse_img; +} + +Color GLTFSpecGloss::get_diffuse_factor() { + return diffuse_factor; +} + +void GLTFSpecGloss::set_diffuse_factor(Color p_diffuse_factor) { + diffuse_factor = p_diffuse_factor; +} + +float GLTFSpecGloss::get_gloss_factor() { + return gloss_factor; +} + +void GLTFSpecGloss::set_gloss_factor(float p_gloss_factor) { + gloss_factor = p_gloss_factor; +} + +Color GLTFSpecGloss::get_specular_factor() { + return specular_factor; +} + +void GLTFSpecGloss::set_specular_factor(Color p_specular_factor) { + specular_factor = p_specular_factor; +} + +Ref GLTFSpecGloss::get_spec_gloss_img() { + return spec_gloss_img; +} + +void GLTFSpecGloss::set_spec_gloss_img(Ref p_spec_gloss_img) { + spec_gloss_img = p_spec_gloss_img; +} diff --git a/modules/gltf/gltf_spec_gloss.h b/modules/gltf/gltf_spec_gloss.h new file mode 100644 index 0000000000..b18d75da00 --- /dev/null +++ b/modules/gltf/gltf_spec_gloss.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* gltf_spec_gloss.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_SPEC_GLOSS_H +#define GLTF_SPEC_GLOSS_H + +#include "core/image.h" +#include "core/resource.h" + +class GLTFSpecGloss : public Resource { + GDCLASS(GLTFSpecGloss, Resource); + friend class GLTFDocument; + +private: + Ref diffuse_img = nullptr; + Color diffuse_factor = Color(1.0f, 1.0f, 1.0f); + float gloss_factor = 1.0f; + Color specular_factor = Color(1.0f, 1.0f, 1.0f); + Ref spec_gloss_img = nullptr; + +protected: + static void _bind_methods(); + +public: + Ref get_diffuse_img(); + void set_diffuse_img(Ref p_diffuse_img); + + Color get_diffuse_factor(); + void set_diffuse_factor(Color p_diffuse_factor); + + float get_gloss_factor(); + void set_gloss_factor(float p_gloss_factor); + + Color get_specular_factor(); + void set_specular_factor(Color p_specular_factor); + + Ref get_spec_gloss_img(); + void set_spec_gloss_img(Ref p_spec_gloss_img); +}; +#endif // GLTF_SPEC_GLOSS_H diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp new file mode 100644 index 0000000000..b99f4a459c --- /dev/null +++ b/modules/gltf/gltf_state.cpp @@ -0,0 +1,307 @@ +/*************************************************************************/ +/* gltf_state.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_state.h" + +void GLTFState::_bind_methods() { + 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); + ClassDB::bind_method(D_METHOD("set_major_version", "major_version"), &GLTFState::set_major_version); + ClassDB::bind_method(D_METHOD("get_minor_version"), &GLTFState::get_minor_version); + ClassDB::bind_method(D_METHOD("set_minor_version", "minor_version"), &GLTFState::set_minor_version); + ClassDB::bind_method(D_METHOD("get_glb_data"), &GLTFState::get_glb_data); + ClassDB::bind_method(D_METHOD("set_glb_data", "glb_data"), &GLTFState::set_glb_data); + ClassDB::bind_method(D_METHOD("get_use_named_skin_binds"), &GLTFState::get_use_named_skin_binds); + ClassDB::bind_method(D_METHOD("set_use_named_skin_binds", "use_named_skin_binds"), &GLTFState::set_use_named_skin_binds); + ClassDB::bind_method(D_METHOD("get_nodes"), &GLTFState::get_nodes); + ClassDB::bind_method(D_METHOD("set_nodes", "nodes"), &GLTFState::set_nodes); + ClassDB::bind_method(D_METHOD("get_buffers"), &GLTFState::get_buffers); + ClassDB::bind_method(D_METHOD("set_buffers", "buffers"), &GLTFState::set_buffers); + ClassDB::bind_method(D_METHOD("get_buffer_views"), &GLTFState::get_buffer_views); + ClassDB::bind_method(D_METHOD("set_buffer_views", "buffer_views"), &GLTFState::set_buffer_views); + ClassDB::bind_method(D_METHOD("get_accessors"), &GLTFState::get_accessors); + ClassDB::bind_method(D_METHOD("set_accessors", "accessors"), &GLTFState::set_accessors); + ClassDB::bind_method(D_METHOD("get_meshes"), &GLTFState::get_meshes); + ClassDB::bind_method(D_METHOD("set_meshes", "meshes"), &GLTFState::set_meshes); + ClassDB::bind_method(D_METHOD("get_animation_players_count", "idx"), &GLTFState::get_animation_players_count); + ClassDB::bind_method(D_METHOD("get_animation_player", "idx"), &GLTFState::get_animation_player); + ClassDB::bind_method(D_METHOD("get_materials"), &GLTFState::get_materials); + ClassDB::bind_method(D_METHOD("set_materials", "materials"), &GLTFState::set_materials); + ClassDB::bind_method(D_METHOD("get_scene_name"), &GLTFState::get_scene_name); + ClassDB::bind_method(D_METHOD("set_scene_name", "scene_name"), &GLTFState::set_scene_name); + ClassDB::bind_method(D_METHOD("get_root_nodes"), &GLTFState::get_root_nodes); + ClassDB::bind_method(D_METHOD("set_root_nodes", "root_nodes"), &GLTFState::set_root_nodes); + ClassDB::bind_method(D_METHOD("get_textures"), &GLTFState::get_textures); + ClassDB::bind_method(D_METHOD("set_textures", "textures"), &GLTFState::set_textures); + ClassDB::bind_method(D_METHOD("get_images"), &GLTFState::get_images); + ClassDB::bind_method(D_METHOD("set_images", "images"), &GLTFState::set_images); + ClassDB::bind_method(D_METHOD("get_skins"), &GLTFState::get_skins); + ClassDB::bind_method(D_METHOD("set_skins", "skins"), &GLTFState::set_skins); + ClassDB::bind_method(D_METHOD("get_cameras"), &GLTFState::get_cameras); + ClassDB::bind_method(D_METHOD("set_cameras", "cameras"), &GLTFState::set_cameras); + ClassDB::bind_method(D_METHOD("get_lights"), &GLTFState::get_lights); + ClassDB::bind_method(D_METHOD("set_lights", "lights"), &GLTFState::set_lights); + ClassDB::bind_method(D_METHOD("get_unique_names"), &GLTFState::get_unique_names); + ClassDB::bind_method(D_METHOD("set_unique_names", "unique_names"), &GLTFState::set_unique_names); + ClassDB::bind_method(D_METHOD("get_unique_animation_names"), &GLTFState::get_unique_animation_names); + ClassDB::bind_method(D_METHOD("set_unique_animation_names", "unique_animation_names"), &GLTFState::set_unique_animation_names); + ClassDB::bind_method(D_METHOD("get_skeletons"), &GLTFState::get_skeletons); + ClassDB::bind_method(D_METHOD("set_skeletons", "skeletons"), &GLTFState::set_skeletons); + ClassDB::bind_method(D_METHOD("get_skeleton_to_node"), &GLTFState::get_skeleton_to_node); + ClassDB::bind_method(D_METHOD("set_skeleton_to_node", "skeleton_to_node"), &GLTFState::set_skeleton_to_node); + 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); + + 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 + ADD_PROPERTY(PropertyInfo(Variant::INT, "minor_version"), "set_minor_version", "get_minor_version"); // int + ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "glb_data"), "set_glb_data", "get_glb_data"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_named_skin_binds"), "set_use_named_skin_binds", "get_use_named_skin_binds"); // bool + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_nodes", "get_nodes"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "buffers"), "set_buffers", "get_buffers"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "buffer_views", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_buffer_views", "get_buffer_views"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "accessors", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_accessors", "get_accessors"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "meshes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_meshes", "get_meshes"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "materials", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_materials", "get_materials"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_name"), "set_scene_name", "get_scene_name"); // String + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "root_nodes"), "set_root_nodes", "get_root_nodes"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_textures", "get_textures"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "images", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_images", "get_images"); // Vector + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skins", "get_skins"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "cameras", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_cameras", "get_cameras"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lights", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_lights", "get_lights"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "unique_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_unique_names", "get_unique_names"); // Set + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "unique_animation_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_unique_animation_names", "get_unique_animation_names"); // Set + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skeletons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeletons", "get_skeletons"); // Vector> + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "skeleton_to_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeleton_to_node", "get_skeleton_to_node"); // Map> +} + +Dictionary GLTFState::get_json() { + return json; +} + +void GLTFState::set_json(Dictionary p_json) { + json = p_json; +} + +int GLTFState::get_major_version() { + return major_version; +} + +void GLTFState::set_major_version(int p_major_version) { + major_version = p_major_version; +} + +int GLTFState::get_minor_version() { + return minor_version; +} + +void GLTFState::set_minor_version(int p_minor_version) { + minor_version = p_minor_version; +} + +Vector GLTFState::get_glb_data() { + return glb_data; +} + +void GLTFState::set_glb_data(Vector p_glb_data) { + glb_data = p_glb_data; +} + +bool GLTFState::get_use_named_skin_binds() { + return use_named_skin_binds; +} + +void GLTFState::set_use_named_skin_binds(bool p_use_named_skin_binds) { + use_named_skin_binds = p_use_named_skin_binds; +} + +Array GLTFState::get_nodes() { + return GLTFDocument::to_array(nodes); +} + +void GLTFState::set_nodes(Array p_nodes) { + GLTFDocument::set_from_array(nodes, p_nodes); +} + +Array GLTFState::get_buffers() { + return GLTFDocument::to_array(buffers); +} + +void GLTFState::set_buffers(Array p_buffers) { + GLTFDocument::set_from_array(buffers, p_buffers); +} + +Array GLTFState::get_buffer_views() { + return GLTFDocument::to_array(buffer_views); +} + +void GLTFState::set_buffer_views(Array p_buffer_views) { + GLTFDocument::set_from_array(buffer_views, p_buffer_views); +} + +Array GLTFState::get_accessors() { + return GLTFDocument::to_array(accessors); +} + +void GLTFState::set_accessors(Array p_accessors) { + GLTFDocument::set_from_array(accessors, p_accessors); +} + +Array GLTFState::get_meshes() { + return GLTFDocument::to_array(meshes); +} + +void GLTFState::set_meshes(Array p_meshes) { + GLTFDocument::set_from_array(meshes, p_meshes); +} + +Array GLTFState::get_materials() { + return GLTFDocument::to_array(materials); +} + +void GLTFState::set_materials(Array p_materials) { + GLTFDocument::set_from_array(materials, p_materials); +} + +String GLTFState::get_scene_name() { + return scene_name; +} + +void GLTFState::set_scene_name(String p_scene_name) { + scene_name = p_scene_name; +} + +Array GLTFState::get_root_nodes() { + return GLTFDocument::to_array(root_nodes); +} + +void GLTFState::set_root_nodes(Array p_root_nodes) { + GLTFDocument::set_from_array(root_nodes, p_root_nodes); +} + +Array GLTFState::get_textures() { + return GLTFDocument::to_array(textures); +} + +void GLTFState::set_textures(Array p_textures) { + GLTFDocument::set_from_array(textures, p_textures); +} + +Array GLTFState::get_images() { + return GLTFDocument::to_array(images); +} + +void GLTFState::set_images(Array p_images) { + GLTFDocument::set_from_array(images, p_images); +} + +Array GLTFState::get_skins() { + return GLTFDocument::to_array(skins); +} + +void GLTFState::set_skins(Array p_skins) { + GLTFDocument::set_from_array(skins, p_skins); +} + +Array GLTFState::get_cameras() { + return GLTFDocument::to_array(cameras); +} + +void GLTFState::set_cameras(Array p_cameras) { + GLTFDocument::set_from_array(cameras, p_cameras); +} + +Array GLTFState::get_lights() { + return GLTFDocument::to_array(lights); +} + +void GLTFState::set_lights(Array p_lights) { + GLTFDocument::set_from_array(lights, p_lights); +} + +Array GLTFState::get_unique_names() { + return GLTFDocument::to_array(unique_names); +} + +void GLTFState::set_unique_names(Array p_unique_names) { + GLTFDocument::set_from_array(unique_names, p_unique_names); +} + +Array GLTFState::get_unique_animation_names() { + return GLTFDocument::to_array(unique_animation_names); +} + +void GLTFState::set_unique_animation_names(Array p_unique_animation_names) { + GLTFDocument::set_from_array(unique_animation_names, p_unique_animation_names); +} + +Array GLTFState::get_skeletons() { + return GLTFDocument::to_array(skeletons); +} + +void GLTFState::set_skeletons(Array p_skeletons) { + GLTFDocument::set_from_array(skeletons, p_skeletons); +} + +Dictionary GLTFState::get_skeleton_to_node() { + return GLTFDocument::to_dict(skeleton_to_node); +} + +void GLTFState::set_skeleton_to_node(Dictionary p_skeleton_to_node) { + GLTFDocument::set_from_dict(skeleton_to_node, p_skeleton_to_node); +} + +Array GLTFState::get_animations() { + return GLTFDocument::to_array(animations); +} + +void GLTFState::set_animations(Array p_animations) { + GLTFDocument::set_from_array(animations, p_animations); +} + +Node *GLTFState::get_scene_node(GLTFNodeIndex idx) { + if (!scene_nodes.has(idx)) { + return nullptr; + } + return scene_nodes[idx]; +} + +int GLTFState::get_animation_players_count(int idx) { + return animation_players.size(); +} + +AnimationPlayer *GLTFState::get_animation_player(int idx) { + ERR_FAIL_INDEX_V(idx, animation_players.size(), nullptr); + return animation_players[idx]; +} diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h new file mode 100644 index 0000000000..646a7c19c3 --- /dev/null +++ b/modules/gltf/gltf_state.h @@ -0,0 +1,186 @@ +/*************************************************************************/ +/* gltf_state.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_STATE_H +#define GLTF_STATE_H + +#include "core/resource.h" +#include "core/vector.h" +#include "editor_scene_importer_gltf.h" +#include "gltf_accessor.h" +#include "gltf_animation.h" +#include "gltf_buffer_view.h" +#include "gltf_camera.h" +#include "gltf_document.h" +#include "gltf_light.h" +#include "gltf_mesh.h" +#include "gltf_node.h" +#include "gltf_skeleton.h" +#include "gltf_skin.h" +#include "gltf_texture.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/texture.h" + +class GLTFState : public Resource { + GDCLASS(GLTFState, Resource); + friend class GLTFDocument; + friend class PackedSceneGLTF; + + String filename; + Dictionary json; + int major_version = 0; + int minor_version = 0; + Vector glb_data; + + bool use_named_skin_binds = false; + bool use_legacy_names = false; + + Vector> nodes; + Vector> buffers; + Vector> buffer_views; + Vector> accessors; + + Vector> meshes; // meshes are loaded directly, no reason not to. + + Vector animation_players; + Map, GLTFMaterialIndex> material_cache; + Vector> materials; + + String scene_name; + Vector root_nodes; + Vector> textures; + Vector> images; + + Vector> skins; + Vector> cameras; + Vector> lights; + Set unique_names; + Set unique_animation_names; + + Vector> skeletons; + Map skeleton_to_node; + Vector> animations; + Map scene_nodes; + +protected: + static void _bind_methods(); + +public: + Dictionary get_json(); + void set_json(Dictionary p_json); + + int get_major_version(); + void set_major_version(int p_major_version); + + int get_minor_version(); + void set_minor_version(int p_minor_version); + + Vector get_glb_data(); + void set_glb_data(Vector p_glb_data); + + bool get_use_named_skin_binds(); + void set_use_named_skin_binds(bool p_use_named_skin_binds); + + Array get_nodes(); + void set_nodes(Array p_nodes); + + Array get_buffers(); + void set_buffers(Array p_buffers); + + Array get_buffer_views(); + void set_buffer_views(Array p_buffer_views); + + Array get_accessors(); + void set_accessors(Array p_accessors); + + Array get_meshes(); + void set_meshes(Array p_meshes); + + Array get_materials(); + void set_materials(Array p_materials); + + String get_scene_name(); + void set_scene_name(String p_scene_name); + + Array get_root_nodes(); + void set_root_nodes(Array p_root_nodes); + + Array get_textures(); + void set_textures(Array p_textures); + + Array get_images(); + void set_images(Array p_images); + + Array get_skins(); + void set_skins(Array p_skins); + + Array get_cameras(); + void set_cameras(Array p_cameras); + + Array get_lights(); + void set_lights(Array p_lights); + + Array get_unique_names(); + void set_unique_names(Array p_unique_names); + + Array get_unique_animation_names(); + void set_unique_animation_names(Array p_unique_names); + + Array get_skeletons(); + void set_skeletons(Array p_skeletons); + + Dictionary get_skeleton_to_node(); + void set_skeleton_to_node(Dictionary p_skeleton_to_node); + + Array get_animations(); + void set_animations(Array p_animations); + + Node *get_scene_node(GLTFNodeIndex idx); + + int get_animation_players_count(int idx); + + AnimationPlayer *get_animation_player(int idx); + + //void set_scene_nodes(Map p_scene_nodes) { + // this->scene_nodes = p_scene_nodes; + //} + + //void set_animation_players(Vector p_animation_players) { + // this->animation_players = p_animation_players; + //} + + //Map, GLTFMaterialIndex> get_material_cache() { + // return this->material_cache; + //} + //void set_material_cache(Map, GLTFMaterialIndex> p_material_cache) { + // this->material_cache = p_material_cache; + //} +}; +#endif // GLTF_STATE_H diff --git a/modules/gltf/gltf_texture.cpp b/modules/gltf/gltf_texture.cpp new file mode 100644 index 0000000000..0482c1064e --- /dev/null +++ b/modules/gltf/gltf_texture.cpp @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* gltf_texture.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_texture.h" + +void GLTFTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_src_image"), &GLTFTexture::get_src_image); + ClassDB::bind_method(D_METHOD("set_src_image", "src_image"), &GLTFTexture::set_src_image); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "src_image"), "set_src_image", "get_src_image"); // int +} + +GLTFImageIndex GLTFTexture::get_src_image() const { + return src_image; +} + +void GLTFTexture::set_src_image(GLTFImageIndex val) { + src_image = val; +} diff --git a/modules/gltf/gltf_texture.h b/modules/gltf/gltf_texture.h new file mode 100644 index 0000000000..0c57afeaf8 --- /dev/null +++ b/modules/gltf/gltf_texture.h @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* gltf_texture.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_TEXTURE_H +#define GLTF_TEXTURE_H + +#include "core/resource.h" +#include "gltf_document.h" + +class GLTFTexture : public Resource { + GDCLASS(GLTFTexture, Resource); + +private: + GLTFImageIndex src_image = 0; + +protected: + static void _bind_methods(); + +public: + GLTFImageIndex get_src_image() const; + void set_src_image(GLTFImageIndex val); +}; + +#endif // GLTF_TEXTURE_H diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp new file mode 100644 index 0000000000..dc995c9249 --- /dev/null +++ b/modules/gltf/register_types.cpp @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "register_types.h" + +#include "editor/editor_node.h" +#include "editor_scene_exporter_gltf_plugin.h" +#include "editor_scene_importer_gltf.h" +#include "gltf_accessor.h" +#include "gltf_animation.h" +#include "gltf_buffer_view.h" +#include "gltf_camera.h" +#include "gltf_document.h" +#include "gltf_light.h" +#include "gltf_mesh.h" +#include "gltf_node.h" +#include "gltf_skeleton.h" +#include "gltf_skin.h" +#include "gltf_spec_gloss.h" +#include "gltf_state.h" +#include "gltf_texture.h" + +#ifndef _3D_DISABLED +#ifdef TOOLS_ENABLED +static void _editor_init() { + Ref import_gltf; + import_gltf.instance(); + ResourceImporterScene::get_singleton()->add_importer(import_gltf); +} +#endif +#endif + +void register_gltf_types() { +#ifndef _3D_DISABLED +#ifdef TOOLS_ENABLED + ClassDB::APIType prev_api = ClassDB::get_current_api(); + ClassDB::set_current_api(ClassDB::API_EDITOR); + ClassDB::register_class(); + ClassDB::register_class(); + EditorPlugins::add_by_type(); + ClassDB::set_current_api(prev_api); + EditorNode::add_init_callback(_editor_init); +#endif + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); +#endif +} + +void unregister_gltf_types() { +} diff --git a/modules/gltf/register_types.h b/modules/gltf/register_types.h new file mode 100644 index 0000000000..fefacb1106 --- /dev/null +++ b/modules/gltf/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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. */ +/*************************************************************************/ + +void register_gltf_types(); +void unregister_gltf_types(); From d699600ec71bbf7733415bcd90ffd4707ed27792 Mon Sep 17 00:00:00 2001 From: Lyuma Date: Fri, 21 May 2021 00:03:06 -0700 Subject: [PATCH 2/2] gltf: Fix mesh nodes which are also bones. Fix issue when two skeletons end up directly parented. Prevent animating TRS for skinned Mesh node. Fix animating weights on meshes with targets but no weights. --- modules/gltf/gltf_document.cpp | 232 ++++++++++++++++----------------- modules/gltf/gltf_document.h | 6 +- modules/gltf/gltf_node.cpp | 11 -- modules/gltf/gltf_node.h | 4 - 4 files changed, 119 insertions(+), 134 deletions(-) diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index f8e30a889c..6594bba65e 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -2894,8 +2894,8 @@ Error GLTFDocument::_parse_meshes(Ref state) { } blend_weights.write[j] = weights[j]; } - mesh->set_blend_weights(blend_weights); } + mesh->set_blend_weights(blend_weights); mesh->set_mesh(import_mesh); state->meshes.push_back(mesh); @@ -4196,80 +4196,10 @@ Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref state, subtree_set.get_members(subtree_nodes, subtree_root); for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) { - ERR_FAIL_COND_V(_reparent_to_fake_joint(state, skeleton, subtree_nodes[subtree_i]), FAILED); - - // We modified the tree, recompute all the heights - _compute_node_heights(state); - } - } - - return OK; -} - -Error GLTFDocument::_reparent_to_fake_joint(Ref state, Ref skeleton, const GLTFNodeIndex node_index) { - Ref node = state->nodes[node_index]; - - // Can we just "steal" this joint if it is just a spatial node? - if (node->skin < 0 && node->mesh < 0 && node->camera < 0) { - node->joint = true; - // Add the joint to the skeletons joints - skeleton->joints.push_back(node_index); - return OK; - } - - GLTFNode *fake_joint = memnew(GLTFNode); - const GLTFNodeIndex fake_joint_index = state->nodes.size(); - state->nodes.push_back(fake_joint); - - // We better not be a joint, or we messed up in our logic - if (node->joint) - return FAILED; - - fake_joint->translation = node->translation; - fake_joint->rotation = node->rotation; - fake_joint->scale = node->scale; - fake_joint->xform = node->xform; - fake_joint->joint = true; - - // We can use the exact same name here, because the joint will be inside a skeleton and not the scene - fake_joint->set_name(node->get_name()); - - // Clear the nodes transforms, since it will be parented to the fake joint - node->translation = Vector3(0, 0, 0); - node->rotation = Quat(); - node->scale = Vector3(1, 1, 1); - node->xform = Transform(); - - // Transfer the node children to the fake joint - for (int child_i = 0; child_i < node->children.size(); ++child_i) { - Ref child = state->nodes[node->children[child_i]]; - child->parent = fake_joint_index; - } - - fake_joint->children = node->children; - node->children.clear(); - - // add the fake joint to the parent and remove the original joint - if (node->parent >= 0) { - Ref parent = state->nodes[node->parent]; - parent->children.erase(node_index); - parent->children.push_back(fake_joint_index); - fake_joint->parent = node->parent; - } - - // Add the node to the fake joint - fake_joint->children.push_back(node_index); - node->parent = fake_joint_index; - node->fake_joint_parent = fake_joint_index; - - // Add the fake joint to the skeletons joints - skeleton->joints.push_back(fake_joint_index); - - // Replace skin_skeletons with fake joints if we must. - for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { - Ref skin = state->skins.write[skin_i]; - if (skin->skin_root == node_index) { - skin->skin_root = fake_joint_index; + Ref node = state->nodes[subtree_nodes[subtree_i]]; + node->joint = true; + // Add the joint to the skeletons joints + skeleton->joints.push_back(subtree_nodes[subtree_i]); } } @@ -5016,9 +4946,9 @@ void GLTFDocument::_assign_scene_names(Ref state) { } } -BoneAttachment *GLTFDocument::_generate_bone_attachment(Ref state, Skeleton *skeleton, const GLTFNodeIndex node_index) { +BoneAttachment *GLTFDocument::_generate_bone_attachment(Ref state, Skeleton *skeleton, const GLTFNodeIndex node_index, const GLTFNodeIndex bone_index) { Ref gltf_node = state->nodes[node_index]; - Ref bone_node = state->nodes[gltf_node->parent]; + Ref bone_node = state->nodes[bone_index]; BoneAttachment *bone_attachment = memnew(BoneAttachment); print_verbose("glTF: Creating bone attachment for: " + gltf_node->get_name()); @@ -5106,7 +5036,7 @@ Spatial *GLTFDocument::_generate_mesh_instance(Ref state, Node *scene return mi; } -Light *GLTFDocument::_generate_light(Ref state, Node *scene_parent, const GLTFNodeIndex node_index) { +Spatial *GLTFDocument::_generate_light(Ref state, Node *scene_parent, const GLTFNodeIndex node_index) { Ref gltf_node = state->nodes[node_index]; ERR_FAIL_INDEX_V(gltf_node->light, state->lights.size(), nullptr); @@ -5155,7 +5085,7 @@ Light *GLTFDocument::_generate_light(Ref state, Node *scene_parent, c light->set_param(SpotLight::PARAM_SPOT_ATTENUATION, angle_attenuation); return light; } - return nullptr; + return memnew(Spatial); } Camera *GLTFDocument::_generate_camera(Ref state, Node *scene_parent, const GLTFNodeIndex node_index) { @@ -5526,32 +5456,102 @@ void GLTFDocument::_convert_mesh_to_gltf(Node *p_scene_parent, Ref st void GLTFDocument::_generate_scene_node(Ref state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) { Ref gltf_node = state->nodes[node_index]; + if (gltf_node->skeleton >= 0) { + _generate_skeleton_bone_node(state, scene_parent, scene_root, node_index); + return; + } + Spatial *current_node = nullptr; // Is our parent a skeleton Skeleton *active_skeleton = Object::cast_to(scene_parent); - if (gltf_node->skeleton >= 0) { - Skeleton *skeleton = state->skeletons[gltf_node->skeleton]->godot_skeleton; - - if (active_skeleton != skeleton) { - ERR_FAIL_COND_MSG(active_skeleton != nullptr, "glTF: Generating scene detected direct parented Skeletons"); - - // 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); - } - } - - active_skeleton = skeleton; - current_node = skeleton; - } + const bool non_bone_parented_to_skeleton = active_skeleton; // If we have an active skeleton, and the node is node skinned, we need to create a bone attachment - if (current_node == nullptr || gltf_node->mesh >= 0 || gltf_node->camera >= 0 || gltf_node->light >= 0) { - if (active_skeleton != nullptr && gltf_node->skin < 0) { - BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index); + if (non_bone_parented_to_skeleton && gltf_node->skin < 0) { + // Bone Attachment - Parent Case + BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, gltf_node->parent); + + scene_parent->add_child(bone_attachment); + bone_attachment->set_owner(scene_root); + + // There is no gltf_node that represent this, so just directly create a unique name + bone_attachment->set_name(_gen_unique_name(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; + } + + // We still have not managed to make a node + if (gltf_node->mesh >= 0) { + current_node = _generate_mesh_instance(state, scene_parent, node_index); + } else if (gltf_node->camera >= 0) { + current_node = _generate_camera(state, scene_parent, node_index); + } else if (gltf_node->light >= 0) { + current_node = _generate_light(state, scene_parent, node_index); + } else { + current_node = _generate_spatial(state, scene_parent, node_index); + } + + scene_parent->add_child(current_node); + if (current_node != scene_root) { + current_node->set_owner(scene_root); + } + current_node->set_transform(gltf_node->xform); + current_node->set_name(gltf_node->get_name()); + + state->scene_nodes.insert(node_index, current_node); + + for (int i = 0; i < gltf_node->children.size(); ++i) { + _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]); + } +} + +void GLTFDocument::_generate_skeleton_bone_node(Ref state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) { + Ref gltf_node = state->nodes[node_index]; + + Spatial *current_node = nullptr; + + Skeleton *skeleton = state->skeletons[gltf_node->skeleton]->godot_skeleton; + // In this case, this node is already a bone in skeleton. + 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); + if (active_skeleton != skeleton) { + if (active_skeleton) { + // Bone Attachment - Direct Parented Skeleton Case + BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, gltf_node->parent); + + scene_parent->add_child(bone_attachment); + bone_attachment->set_owner(scene_root); + + // There is no gltf_node that represent this, so just directly create a unique name + bone_attachment->set_name(_gen_unique_name(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)); + } + + // 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); + } + } + + active_skeleton = skeleton; + current_node = skeleton; + + if (requires_extra_node) { + // 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(state, active_skeleton, node_index, node_index); scene_parent->add_child(bone_attachment); bone_attachment->set_owner(scene_root); @@ -5573,17 +5573,11 @@ void GLTFDocument::_generate_scene_node(Ref state, Node *scene_parent current_node = _generate_light(state, scene_parent, node_index); } - if (!current_node) { - current_node = _generate_spatial(state, scene_parent, node_index); - } - scene_parent->add_child(current_node); if (current_node != scene_root) { current_node->set_owner(scene_root); } - if (use_xform) { - current_node->set_transform(gltf_node->xform); - } + // Do not set transform here. Transform is already applied to our bone. if (state->use_legacy_names) { current_node->set_name(_legacy_validate_node_name(gltf_node->get_name())); } else { @@ -5594,7 +5588,7 @@ void GLTFDocument::_generate_scene_node(Ref state, Node *scene_parent state->scene_nodes.insert(node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { - _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]); + _generate_scene_node(state, active_skeleton, scene_root, gltf_node->children[i]); } } @@ -5735,28 +5729,30 @@ void GLTFDocument::_import_animation(Ref state, AnimationPlayer *ap, for (Map::Element *track_i = anim->get_tracks().front(); track_i; track_i = track_i->next()) { const GLTFAnimation::Track &track = track_i->get(); - //need to find the path + //need to find the path: for skeletons, weight tracks will affect the mesh NodePath node_path; + //for skeletons, transform tracks always affect bones + NodePath transform_node_path; GLTFNodeIndex node_index = track_i->key(); - if (state->nodes[node_index]->fake_joint_parent >= 0) { - // Should be same as parent - node_index = state->nodes[node_index]->fake_joint_parent; - } const Ref gltf_node = state->nodes[track_i->key()]; + Node *root = ap->get_parent(); + ERR_FAIL_COND(root == nullptr); + Map::Element *node_element = state->scene_nodes.find(node_index); + ERR_CONTINUE_MSG(node_element == nullptr, vformat("Unable to find node %d for animation", node_index)); + node_path = root->get_path_to(node_element->get()); + if (gltf_node->skeleton >= 0) { const Skeleton *sk = state->skeletons[gltf_node->skeleton]->godot_skeleton; ERR_FAIL_COND(sk == nullptr); const String path = ap->get_parent()->get_path_to(sk); const String bone = gltf_node->get_name(); - node_path = path + ":" + bone; + transform_node_path = path + ":" + bone; } else { - Node *root = ap->get_parent(); - Node *godot_node = state->scene_nodes.find(node_index)->get(); - node_path = root->get_path_to(godot_node); + transform_node_path = node_path; } for (int i = 0; i < track.rotation_track.times.size(); i++) { @@ -5775,11 +5771,13 @@ void GLTFDocument::_import_animation(Ref state, AnimationPlayer *ap, } } - if (track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) { + // Animated TRS properties will not affect a skinned mesh. + const bool transform_affects_skinned_mesh_instance = gltf_node->skeleton < 0 && gltf_node->skin >= 0; + if ((track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) && !transform_affects_skinned_mesh_instance) { //make transform track int track_idx = animation->get_track_count(); animation->add_track(Animation::TYPE_TRANSFORM); - animation->track_set_path(track_idx, node_path); + animation->track_set_path(track_idx, transform_node_path); //first determine animation length const double increment = 1.0 / bake_fps; diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index 993d870be9..31e2324b9b 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -258,11 +258,12 @@ private: Error _serialize_animations(Ref state); BoneAttachment *_generate_bone_attachment(Ref state, Skeleton *skeleton, - const GLTFNodeIndex node_index); + const GLTFNodeIndex node_index, + const GLTFNodeIndex bone_index); Spatial *_generate_mesh_instance(Ref state, Node *scene_parent, const GLTFNodeIndex node_index); Camera *_generate_camera(Ref state, Node *scene_parent, const GLTFNodeIndex node_index); - Light *_generate_light(Ref state, Node *scene_parent, const GLTFNodeIndex node_index); + Spatial *_generate_light(Ref state, Node *scene_parent, const GLTFNodeIndex node_index); Spatial *_generate_spatial(Ref state, Node *scene_parent, const GLTFNodeIndex node_index); void _assign_scene_names(Ref state); @@ -366,6 +367,7 @@ public: void _generate_scene_node(Ref state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index); + void _generate_skeleton_bone_node(Ref state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index); void _import_animation(Ref state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps); GLTFMeshIndex _convert_mesh_instance(Ref state, diff --git a/modules/gltf/gltf_node.cpp b/modules/gltf/gltf_node.cpp index 0ac0b06510..2beb2f34d1 100644 --- a/modules/gltf/gltf_node.cpp +++ b/modules/gltf/gltf_node.cpp @@ -55,8 +55,6 @@ void GLTFNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_scale", "scale"), &GLTFNode::set_scale); ClassDB::bind_method(D_METHOD("get_children"), &GLTFNode::get_children); ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children); - ClassDB::bind_method(D_METHOD("get_fake_joint_parent"), &GLTFNode::get_fake_joint_parent); - ClassDB::bind_method(D_METHOD("set_fake_joint_parent", "fake_joint_parent"), &GLTFNode::set_fake_joint_parent); ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light); ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light); @@ -72,7 +70,6 @@ void GLTFNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::QUAT, "rotation"), "set_rotation", "get_rotation"); // Quat ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); // Vector3 ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "children"), "set_children", "get_children"); // Vector - ADD_PROPERTY(PropertyInfo(Variant::INT, "fake_joint_parent"), "set_fake_joint_parent", "get_fake_joint_parent"); // GLTFNodeIndex ADD_PROPERTY(PropertyInfo(Variant::INT, "light"), "set_light", "get_light"); // GLTFLightIndex } @@ -172,14 +169,6 @@ void GLTFNode::set_children(Vector p_children) { children = p_children; } -GLTFNodeIndex GLTFNode::get_fake_joint_parent() { - return fake_joint_parent; -} - -void GLTFNode::set_fake_joint_parent(GLTFNodeIndex p_fake_joint_parent) { - fake_joint_parent = p_fake_joint_parent; -} - GLTFLightIndex GLTFNode::get_light() { return light; } diff --git a/modules/gltf/gltf_node.h b/modules/gltf/gltf_node.h index 32e41d037d..f292e151ef 100644 --- a/modules/gltf/gltf_node.h +++ b/modules/gltf/gltf_node.h @@ -53,7 +53,6 @@ private: Quat rotation; Vector3 scale = Vector3(1, 1, 1); Vector children; - GLTFNodeIndex fake_joint_parent = -1; GLTFLightIndex light = -1; protected: @@ -96,9 +95,6 @@ public: Vector get_children(); void set_children(Vector p_children); - GLTFNodeIndex get_fake_joint_parent(); - void set_fake_joint_parent(GLTFNodeIndex p_fake_joint_parent); - GLTFLightIndex get_light(); void set_light(GLTFLightIndex p_light); };