Browse Source

Merge pull request #49120 from lyuma/gltf-module-3.x

Rémi Verschelde 4 years ago
parent
commit
9b35708a21
52 changed files with 10942 additions and 3796 deletions
  1. 0 5
      editor/editor_node.cpp
  2. 0 3344
      editor/import/editor_scene_importer_gltf.cpp
  3. 0 447
      editor/import/editor_scene_importer_gltf.h
  4. 10 0
      modules/gltf/SCsub
  5. 30 0
      modules/gltf/config.py
  6. 13 0
      modules/gltf/doc_classes/EditorSceneImporterGLTF.xml
  7. 43 0
      modules/gltf/doc_classes/GLTFAccessor.xml
  8. 17 0
      modules/gltf/doc_classes/GLTFAnimation.xml
  9. 25 0
      modules/gltf/doc_classes/GLTFBufferView.xml
  10. 23 0
      modules/gltf/doc_classes/GLTFCamera.xml
  11. 13 0
      modules/gltf/doc_classes/GLTFDocument.xml
  12. 27 0
      modules/gltf/doc_classes/GLTFLight.xml
  13. 19 0
      modules/gltf/doc_classes/GLTFMesh.xml
  14. 41 0
      modules/gltf/doc_classes/GLTFNode.xml
  15. 67 0
      modules/gltf/doc_classes/GLTFSkeleton.xml
  16. 71 0
      modules/gltf/doc_classes/GLTFSkin.xml
  17. 25 0
      modules/gltf/doc_classes/GLTFSpecGloss.xml
  18. 265 0
      modules/gltf/doc_classes/GLTFState.xml
  19. 17 0
      modules/gltf/doc_classes/GLTFTexture.xml
  20. 58 0
      modules/gltf/doc_classes/PackedSceneGLTF.xml
  21. 95 0
      modules/gltf/editor_scene_exporter_gltf_plugin.cpp
  22. 55 0
      modules/gltf/editor_scene_exporter_gltf_plugin.h
  23. 187 0
      modules/gltf/editor_scene_importer_gltf.cpp
  24. 96 0
      modules/gltf/editor_scene_importer_gltf.h
  25. 189 0
      modules/gltf/gltf_accessor.cpp
  26. 104 0
      modules/gltf/gltf_accessor.h
  27. 53 0
      modules/gltf/gltf_animation.cpp
  28. 74 0
      modules/gltf/gltf_animation.h
  29. 90 0
      modules/gltf/gltf_buffer_view.cpp
  30. 68 0
      modules/gltf/gltf_buffer_view.h
  31. 47 0
      modules/gltf/gltf_camera.cpp
  32. 58 0
      modules/gltf/gltf_camera.h
  33. 6735 0
      modules/gltf/gltf_document.cpp
  34. 437 0
      modules/gltf/gltf_document.h
  35. 101 0
      modules/gltf/gltf_light.cpp
  36. 72 0
      modules/gltf/gltf_light.h
  37. 57 0
      modules/gltf/gltf_mesh.cpp
  38. 54 0
      modules/gltf/gltf_mesh.h
  39. 178 0
      modules/gltf/gltf_node.cpp
  40. 101 0
      modules/gltf/gltf_node.h
  41. 95 0
      modules/gltf/gltf_skeleton.cpp
  42. 101 0
      modules/gltf/gltf_skeleton.h
  43. 155 0
      modules/gltf/gltf_skin.cpp
  44. 109 0
      modules/gltf/gltf_skin.h
  45. 90 0
      modules/gltf/gltf_spec_gloss.cpp
  46. 67 0
      modules/gltf/gltf_spec_gloss.h
  47. 307 0
      modules/gltf/gltf_state.cpp
  48. 186 0
      modules/gltf/gltf_state.h
  49. 46 0
      modules/gltf/gltf_texture.cpp
  50. 51 0
      modules/gltf/gltf_texture.h
  51. 88 0
      modules/gltf/register_types.cpp
  52. 32 0
      modules/gltf/register_types.h

+ 0 - 5
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<EditorSceneImporterGLTF> import_gltf;
-			import_gltf.instance();
-			import_scene->add_importer(import_gltf);
-
 			Ref<EditorSceneImporterESCN> import_escn;
 			import_escn.instance();
 			import_scene->add_importer(import_escn);

+ 0 - 3344
editor/import/editor_scene_importer_gltf.cpp

@@ -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<String> *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<uint8_t> 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<uint8_t> 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<String> 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<uint8_t> _parse_base64_uri(const String &uri) {
-	int start = uri.find(",");
-	ERR_FAIL_COND_V(start == -1, Vector<uint8_t>());
-
-	CharString substr = uri.right(start + 1).ascii();
-
-	int strlen = substr.length();
-
-	Vector<uint8_t> 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<uint8_t>());
-
-	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<uint8_t> 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 "<Error>";
-}
-
-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<uint8_t> 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<double> 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<double>());
-
-	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<double>());
-	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<double> 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<double>());
-
-		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<double>();
-		}
-
-	} 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<double> 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<double>();
-		}
-
-		Vector<double> 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<double>();
-		}
-
-		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<int> EditorSceneImporterGLTF::_decode_accessor_as_ints(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	PoolVector<int> ret;
-
-	if (attribs.size() == 0) {
-		return ret;
-	}
-
-	const double *attribs_ptr = attribs.ptr();
-	const int ret_size = attribs.size();
-	ret.resize(ret_size);
-	{
-		PoolVector<int>::Write w = ret.write();
-		for (int i = 0; i < ret_size; i++) {
-			w[i] = int(attribs_ptr[i]);
-		}
-	}
-	return ret;
-}
-
-PoolVector<float> EditorSceneImporterGLTF::_decode_accessor_as_floats(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	PoolVector<float> ret;
-
-	if (attribs.size() == 0) {
-		return ret;
-	}
-
-	const double *attribs_ptr = attribs.ptr();
-	const int ret_size = attribs.size();
-	ret.resize(ret_size);
-	{
-		PoolVector<float>::Write w = ret.write();
-		for (int i = 0; i < ret_size; i++) {
-			w[i] = float(attribs_ptr[i]);
-		}
-	}
-	return ret;
-}
-
-PoolVector<Vector2> EditorSceneImporterGLTF::_decode_accessor_as_vec2(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	PoolVector<Vector2> 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<Vector2>::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<Vector3> EditorSceneImporterGLTF::_decode_accessor_as_vec3(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	PoolVector<Vector3> 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<Vector3>::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<Color> EditorSceneImporterGLTF::_decode_accessor_as_color(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	PoolVector<Color> 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<Color>::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<Quat> EditorSceneImporterGLTF::_decode_accessor_as_quat(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	Vector<Quat> 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<Transform2D> EditorSceneImporterGLTF::_decode_accessor_as_xform2d(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	Vector<Transform2D> 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<Basis> EditorSceneImporterGLTF::_decode_accessor_as_basis(GLTFState &state, const GLTFAccessorIndex p_accessor, bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	Vector<Basis> 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<Transform> EditorSceneImporterGLTF::_decode_accessor_as_xform(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
-	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
-	Vector<Transform> 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<float> 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<float>::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<int> 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<int>::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<Vector3> &vertices = array[Mesh::ARRAY_VERTEX];
-				ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR);
-				PoolVector<int> indices;
-				const int vs = vertices.size();
-				indices.resize(vs);
-				{
-					const PoolVector<int>::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<SurfaceTool> 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<Vector3> varr = _decode_accessor_as_vec3(state, t["POSITION"], true);
-						const PoolVector<Vector3> 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<Vector3>::Write w_varr = varr.write();
-							const PoolVector<Vector3>::Read r_varr = varr.read();
-							const PoolVector<Vector3>::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<Vector3> narr = _decode_accessor_as_vec3(state, t["NORMAL"], true);
-						const PoolVector<Vector3> 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<Vector3>::Write w_narr = narr.write();
-							const PoolVector<Vector3>::Read r_narr = narr.read();
-							const PoolVector<Vector3>::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<Vector3> tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true);
-						const PoolVector<float> src_tangents = array[Mesh::ARRAY_TANGENT];
-						ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR);
-
-						PoolVector<float> tangents_v4;
-
-						{
-							int max_idx = tangents_v3.size();
-
-							int size4 = src_tangents.size();
-							tangents_v4.resize(size4);
-							const PoolVector<float>::Write w4 = tangents_v4.write();
-
-							const PoolVector<Vector3>::Read r3 = tangents_v3.read();
-							const PoolVector<float>::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<SurfaceTool> 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<Material> &mat = state.materials[material];
-
-				mesh.mesh->surface_set_material(mesh.mesh->get_surface_count() - 1, mat);
-			} else {
-				Ref<SpatialMaterial> 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<uint8_t> 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<Texture>()); // 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> 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<Texture>()); // 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<Texture>()); // 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<Image> 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<Texture>());
-			continue;
-		}
-
-		Ref<ImageTexture> 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<Texture> EditorSceneImporterGLTF::_get_texture(GLTFState &state, const GLTFTextureIndex p_texture) {
-	ERR_FAIL_INDEX_V(p_texture, state.textures.size(), Ref<Texture>());
-	const GLTFImageIndex image = state.textures[p_texture].src_image;
-
-	ERR_FAIL_INDEX_V(image, state.images.size(), Ref<Texture>());
-
-	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<SpatialMaterial> 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<Texture> 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<GLTFNodeIndex> &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<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFNodeIndex> disjoint_set;
-
-	Vector<GLTFNodeIndex> 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<GLTFNodeIndex> out_owners;
-	disjoint_set.get_representatives(out_owners);
-
-	Vector<GLTFNodeIndex> out_roots;
-
-	for (int i = 0; i < out_owners.size(); ++i) {
-		Vector<GLTFNodeIndex> 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<GLTFNodeIndex> disjoint_set;
-
-	Vector<GLTFNodeIndex> 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<GLTFNodeIndex> out_owners;
-	disjoint_set.get_representatives(out_owners);
-
-	Vector<GLTFNodeIndex> out_roots;
-
-	for (int i = 0; i < out_owners.size(); ++i) {
-		Vector<GLTFNodeIndex> 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<GLTFNodeIndex> skeleton_sets;
-
-	for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) {
-		const GLTFSkin &skin = state.skins[skin_i];
-
-		Vector<GLTFNodeIndex> 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<GLTFNodeIndex> groups_representatives;
-		skeleton_sets.get_representatives(groups_representatives);
-
-		Vector<GLTFNodeIndex> highest_group_members;
-		Vector<Vector<GLTFNodeIndex>> groups;
-		for (int i = 0; i < groups_representatives.size(); ++i) {
-			Vector<GLTFNodeIndex> 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<GLTFNodeIndex> &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<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFNodeIndex> &non_joints) {
-	DisjointSet<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFNodeIndex> owners;
-	disjoint_set.get_representatives(owners);
-
-	Vector<GLTFNodeIndex> roots;
-
-	for (int i = 0; i < owners.size(); ++i) {
-		Vector<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFNodeIndex> 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;
-		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> 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> &skin_a, const Ref<Skin> &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> &skin_i = state.skins[i].godot_skin;
-			const Ref<Skin> &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<float> times = _decode_accessor_as_floats(state, input, false);
-			if (path == "translation") {
-				const PoolVector<Vector3> 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<Quat> 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<Vector3> 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<float> 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<float>::Read r = weights.read();
-				for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea
-					GLTFAnimation::Channel<float> cf;
-					cf.interpolation = interp;
-					cf.times = Variant(times);
-					Vector<float> 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<Skeleton>(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<Skeleton>(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 <class T>
-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> {
-	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 <class T>
-T EditorSceneImporterGLTF::_interpolate_track(const Vector<float> &p_times, const Vector<T> &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<T> 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;
-	animation.instance();
-	animation->set_name(name);
-
-	if (anim.loop) {
-		animation->set_loop(true);
-	}
-
-	float length = 0;
-
-	for (Map<int, GLTFAnimation::Track>::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<GLTFNodeIndex, Node *>::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<Vector3>(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation);
-				}
-
-				if (track.rotation_track.times.size()) {
-					rot = _interpolate_track<Quat>(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation);
-				}
-
-				if (track.scale_track.times.size()) {
-					scale = _interpolate_track<Vector3>(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<float>(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<GLTFNodeIndex, Node *>::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<MeshInstance>(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<String> *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<Animation> EditorSceneImporterGLTF::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) {
-	return Ref<Animation>();
-}
-
-EditorSceneImporterGLTF::EditorSceneImporterGLTF() {
-}

+ 0 - 447
editor/import/editor_scene_importer_gltf.h

@@ -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<int> 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<GLTFNodeIndex> joints;
-
-		// The roots of the skeleton. If there are multiple, each root must have the same parent
-		// (ie roots are siblings)
-		Vector<GLTFNodeIndex> roots;
-
-		// The created Skeleton for the scene
-		Skeleton *godot_skeleton;
-
-		// Set of unique bone names for the skeleton
-		Set<String> 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<GLTFNodeIndex> joints_original;
-		Vector<Transform> 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<GLTFNodeIndex> joints;
-
-		// All Nodes that are caught in-between skin joint nodes, and are not defined
-		// as joints by any skin
-		Vector<GLTFNodeIndex> 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<GLTFNodeIndex> 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<int, int> joint_i_to_bone_i;
-		Map<int, StringName> 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<Skin> godot_skin;
-
-		GLTFSkin() :
-				skin_root(-1),
-				skeleton(-1) {}
-	};
-
-	struct GLTFMesh {
-		Ref<ArrayMesh> mesh;
-		Vector<float> 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 <class T>
-		struct Channel {
-			Interpolation interpolation;
-			Vector<float> times;
-			Vector<T> values;
-		};
-
-		struct Track {
-			Channel<Vector3> translation_track;
-			Channel<Quat> rotation_track;
-			Channel<Vector3> scale_track;
-			Vector<Channel<float>> weight_tracks;
-		};
-
-		String name;
-
-		Map<int, Track> tracks;
-	};
-
-	struct GLTFState {
-		Dictionary json;
-		int major_version;
-		int minor_version;
-		Vector<uint8_t> glb_data;
-
-		bool use_named_skin_binds;
-		bool use_legacy_names;
-
-		Vector<GLTFNode *> nodes;
-		Vector<Vector<uint8_t>> buffers;
-		Vector<GLTFBufferView> buffer_views;
-		Vector<GLTFAccessor> accessors;
-
-		Vector<GLTFMesh> meshes; //meshes are loaded directly, no reason not to.
-		Vector<Ref<Material>> materials;
-
-		String scene_name;
-		Vector<int> root_nodes;
-
-		Vector<GLTFTexture> textures;
-		Vector<Ref<Texture>> images;
-
-		Vector<GLTFSkin> skins;
-		Vector<GLTFCamera> cameras;
-		Vector<GLTFLight> lights;
-
-		Set<String> unique_names;
-		Set<String> unique_animation_names;
-
-		Vector<GLTFSkeleton> skeletons;
-		Vector<GLTFAnimation> animations;
-
-		Map<GLTFNodeIndex, Node *> 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<Texture> _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<double> _decode_accessor(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	PoolVector<float> _decode_accessor_as_floats(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	PoolVector<int> _decode_accessor_as_ints(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	PoolVector<Vector2> _decode_accessor_as_vec2(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	PoolVector<Vector3> _decode_accessor_as_vec3(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	PoolVector<Color> _decode_accessor_as_color(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	Vector<Quat> _decode_accessor_as_quat(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	Vector<Transform2D> _decode_accessor_as_xform2d(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	Vector<Basis> _decode_accessor_as_basis(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex);
-	Vector<Transform> _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<GLTFNodeIndex> &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<GLTFNodeIndex> &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> &skin_a, const Ref<Skin> &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 <class T>
-	T _interpolate_track(const Vector<float> &p_times, const Vector<T> &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<String> *r_extensions) const;
-	virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps = nullptr, Error *r_err = nullptr);
-	virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps);
-
-	EditorSceneImporterGLTF();
-};
-
-#endif // EDITOR_SCENE_IMPORTER_GLTF_H

+ 10 - 0
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")

+ 30 - 0
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"

+ 13 - 0
modules/gltf/doc_classes/EditorSceneImporterGLTF.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorSceneImporterGLTF" inherits="EditorSceneImporter" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 43 - 0
modules/gltf/doc_classes/GLTFAccessor.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFAccessor" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="buffer_view" type="int" setter="set_buffer_view" getter="get_buffer_view" default="0">
+		</member>
+		<member name="byte_offset" type="int" setter="set_byte_offset" getter="get_byte_offset" default="0">
+		</member>
+		<member name="component_type" type="int" setter="set_component_type" getter="get_component_type" default="0">
+		</member>
+		<member name="count" type="int" setter="set_count" getter="get_count" default="0">
+		</member>
+		<member name="max" type="PoolRealArray" setter="set_max" getter="get_max" default="PoolRealArray(  )">
+		</member>
+		<member name="min" type="PoolRealArray" setter="set_min" getter="get_min" default="PoolRealArray(  )">
+		</member>
+		<member name="normalized" type="bool" setter="set_normalized" getter="get_normalized" default="false">
+		</member>
+		<member name="sparse_count" type="int" setter="set_sparse_count" getter="get_sparse_count" default="0">
+		</member>
+		<member name="sparse_indices_buffer_view" type="int" setter="set_sparse_indices_buffer_view" getter="get_sparse_indices_buffer_view" default="0">
+		</member>
+		<member name="sparse_indices_byte_offset" type="int" setter="set_sparse_indices_byte_offset" getter="get_sparse_indices_byte_offset" default="0">
+		</member>
+		<member name="sparse_indices_component_type" type="int" setter="set_sparse_indices_component_type" getter="get_sparse_indices_component_type" default="0">
+		</member>
+		<member name="sparse_values_buffer_view" type="int" setter="set_sparse_values_buffer_view" getter="get_sparse_values_buffer_view" default="0">
+		</member>
+		<member name="sparse_values_byte_offset" type="int" setter="set_sparse_values_byte_offset" getter="get_sparse_values_byte_offset" default="0">
+		</member>
+		<member name="type" type="int" setter="set_type" getter="get_type" default="0">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 17 - 0
modules/gltf/doc_classes/GLTFAnimation.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFAnimation" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="loop" type="bool" setter="set_loop" getter="get_loop" default="false">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 25 - 0
modules/gltf/doc_classes/GLTFBufferView.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFBufferView" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="buffer" type="int" setter="set_buffer" getter="get_buffer" default="-1">
+		</member>
+		<member name="byte_length" type="int" setter="set_byte_length" getter="get_byte_length" default="0">
+		</member>
+		<member name="byte_offset" type="int" setter="set_byte_offset" getter="get_byte_offset" default="0">
+		</member>
+		<member name="byte_stride" type="int" setter="set_byte_stride" getter="get_byte_stride" default="-1">
+		</member>
+		<member name="indices" type="bool" setter="set_indices" getter="get_indices" default="false">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 23 - 0
modules/gltf/doc_classes/GLTFCamera.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFCamera" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="fov_size" type="float" setter="set_fov_size" getter="get_fov_size" default="75.0">
+		</member>
+		<member name="perspective" type="bool" setter="set_perspective" getter="get_perspective" default="true">
+		</member>
+		<member name="zfar" type="float" setter="set_zfar" getter="get_zfar" default="4000.0">
+		</member>
+		<member name="znear" type="float" setter="set_znear" getter="get_znear" default="0.05">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 13 - 0
modules/gltf/doc_classes/GLTFDocument.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFDocument" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 27 - 0
modules/gltf/doc_classes/GLTFLight.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFLight" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="color" type="Color" setter="set_color" getter="get_color" default="Color( 0, 0, 0, 1 )">
+		</member>
+		<member name="inner_cone_angle" type="float" setter="set_inner_cone_angle" getter="get_inner_cone_angle" default="0.0">
+		</member>
+		<member name="intensity" type="float" setter="set_intensity" getter="get_intensity" default="0.0">
+		</member>
+		<member name="outer_cone_angle" type="float" setter="set_outer_cone_angle" getter="get_outer_cone_angle" default="0.0">
+		</member>
+		<member name="range" type="float" setter="set_range" getter="get_range" default="0.0">
+		</member>
+		<member name="type" type="String" setter="set_type" getter="get_type" default="&quot;&quot;">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 19 - 0
modules/gltf/doc_classes/GLTFMesh.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFMesh" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="blend_weights" type="PoolRealArray" setter="set_blend_weights" getter="get_blend_weights" default="PoolRealArray(  )">
+		</member>
+		<member name="mesh" type="ArrayMesh" setter="set_mesh" getter="get_mesh">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 41 - 0
modules/gltf/doc_classes/GLTFNode.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFNode" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="camera" type="int" setter="set_camera" getter="get_camera" default="-1">
+		</member>
+		<member name="children" type="PoolIntArray" setter="set_children" getter="get_children" default="PoolIntArray(  )">
+		</member>
+		<member name="height" type="int" setter="set_height" getter="get_height" default="-1">
+		</member>
+		<member name="joint" type="bool" setter="set_joint" getter="get_joint" default="false">
+		</member>
+		<member name="light" type="int" setter="set_light" getter="get_light" default="-1">
+		</member>
+		<member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1">
+		</member>
+		<member name="parent" type="int" setter="set_parent" getter="get_parent" default="-1">
+		</member>
+		<member name="rotation" type="Quat" setter="set_rotation" getter="get_rotation" default="Quat( 0, 0, 0, 1 )">
+		</member>
+		<member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3( 1, 1, 1 )">
+		</member>
+		<member name="skeleton" type="int" setter="set_skeleton" getter="get_skeleton" default="-1">
+		</member>
+		<member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1">
+		</member>
+		<member name="translation" type="Vector3" setter="set_translation" getter="get_translation" default="Vector3( 0, 0, 0 )">
+		</member>
+		<member name="xform" type="Transform" setter="set_xform" getter="get_xform" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 67 - 0
modules/gltf/doc_classes/GLTFSkeleton.xml

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFSkeleton" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="get_bone_attachment">
+			<return type="BoneAttachment">
+			</return>
+			<argument index="0" name="idx" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_bone_attachment_count">
+			<return type="int">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_godot_bone_node">
+			<return type="Dictionary">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_godot_skeleton">
+			<return type="Skeleton">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_unique_names">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="set_godot_bone_node">
+			<return type="void">
+			</return>
+			<argument index="0" name="godot_bone_node" type="Dictionary">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_unique_names">
+			<return type="void">
+			</return>
+			<argument index="0" name="unique_names" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+	</methods>
+	<members>
+		<member name="joints" type="PoolIntArray" setter="set_joints" getter="get_joints" default="PoolIntArray(  )">
+		</member>
+		<member name="roots" type="PoolIntArray" setter="set_roots" getter="get_roots" default="PoolIntArray(  )">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 71 - 0
modules/gltf/doc_classes/GLTFSkin.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFSkin" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="get_inverse_binds">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_joint_i_to_bone_i">
+			<return type="Dictionary">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_joint_i_to_name">
+			<return type="Dictionary">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="set_inverse_binds">
+			<return type="void">
+			</return>
+			<argument index="0" name="inverse_binds" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_joint_i_to_bone_i">
+			<return type="void">
+			</return>
+			<argument index="0" name="joint_i_to_bone_i" type="Dictionary">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_joint_i_to_name">
+			<return type="void">
+			</return>
+			<argument index="0" name="joint_i_to_name" type="Dictionary">
+			</argument>
+			<description>
+			</description>
+		</method>
+	</methods>
+	<members>
+		<member name="godot_skin" type="Skin" setter="set_godot_skin" getter="get_godot_skin">
+		</member>
+		<member name="joints" type="PoolIntArray" setter="set_joints" getter="get_joints" default="PoolIntArray(  )">
+		</member>
+		<member name="joints_original" type="PoolIntArray" setter="set_joints_original" getter="get_joints_original" default="PoolIntArray(  )">
+		</member>
+		<member name="non_joints" type="PoolIntArray" setter="set_non_joints" getter="get_non_joints" default="PoolIntArray(  )">
+		</member>
+		<member name="roots" type="PoolIntArray" setter="set_roots" getter="get_roots" default="PoolIntArray(  )">
+		</member>
+		<member name="skeleton" type="int" setter="set_skeleton" getter="get_skeleton" default="-1">
+		</member>
+		<member name="skin_root" type="int" setter="set_skin_root" getter="get_skin_root" default="-1">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 25 - 0
modules/gltf/doc_classes/GLTFSpecGloss.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFSpecGloss" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="diffuse_factor" type="Color" setter="set_diffuse_factor" getter="get_diffuse_factor" default="Color( 1, 1, 1, 1 )">
+		</member>
+		<member name="diffuse_img" type="Image" setter="set_diffuse_img" getter="get_diffuse_img">
+		</member>
+		<member name="gloss_factor" type="float" setter="set_gloss_factor" getter="get_gloss_factor" default="1.0">
+		</member>
+		<member name="spec_gloss_img" type="Image" setter="set_spec_gloss_img" getter="get_spec_gloss_img">
+		</member>
+		<member name="specular_factor" type="Color" setter="set_specular_factor" getter="get_specular_factor" default="Color( 1, 1, 1, 1 )">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 265 - 0
modules/gltf/doc_classes/GLTFState.xml

@@ -0,0 +1,265 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFState" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="get_accessors">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_animation_player">
+			<return type="AnimationPlayer">
+			</return>
+			<argument index="0" name="idx" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_animation_players_count">
+			<return type="int">
+			</return>
+			<argument index="0" name="idx" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_animations">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_buffer_views">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_cameras">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_images">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_lights">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_materials">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_meshes">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_nodes">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_scene_node">
+			<return type="Node">
+			</return>
+			<argument index="0" name="idx" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_skeleton_to_node">
+			<return type="Dictionary">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_skeletons">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_skins">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_textures">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_unique_animation_names">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_unique_names">
+			<return type="Array">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="set_accessors">
+			<return type="void">
+			</return>
+			<argument index="0" name="accessors" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_animations">
+			<return type="void">
+			</return>
+			<argument index="0" name="animations" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_buffer_views">
+			<return type="void">
+			</return>
+			<argument index="0" name="buffer_views" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_cameras">
+			<return type="void">
+			</return>
+			<argument index="0" name="cameras" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_images">
+			<return type="void">
+			</return>
+			<argument index="0" name="images" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_lights">
+			<return type="void">
+			</return>
+			<argument index="0" name="lights" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_materials">
+			<return type="void">
+			</return>
+			<argument index="0" name="materials" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_meshes">
+			<return type="void">
+			</return>
+			<argument index="0" name="meshes" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_nodes">
+			<return type="void">
+			</return>
+			<argument index="0" name="nodes" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_skeleton_to_node">
+			<return type="void">
+			</return>
+			<argument index="0" name="skeleton_to_node" type="Dictionary">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_skeletons">
+			<return type="void">
+			</return>
+			<argument index="0" name="skeletons" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_skins">
+			<return type="void">
+			</return>
+			<argument index="0" name="skins" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_textures">
+			<return type="void">
+			</return>
+			<argument index="0" name="textures" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_unique_animation_names">
+			<return type="void">
+			</return>
+			<argument index="0" name="unique_animation_names" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_unique_names">
+			<return type="void">
+			</return>
+			<argument index="0" name="unique_names" type="Array">
+			</argument>
+			<description>
+			</description>
+		</method>
+	</methods>
+	<members>
+		<member name="buffers" type="Array" setter="set_buffers" getter="get_buffers" default="[  ]">
+		</member>
+		<member name="glb_data" type="PoolByteArray" setter="set_glb_data" getter="get_glb_data" default="PoolByteArray(  )">
+		</member>
+		<member name="json" type="Dictionary" setter="set_json" getter="get_json" default="{}">
+		</member>
+		<member name="major_version" type="int" setter="set_major_version" getter="get_major_version" default="0">
+		</member>
+		<member name="minor_version" type="int" setter="set_minor_version" getter="get_minor_version" default="0">
+		</member>
+		<member name="root_nodes" type="Array" setter="set_root_nodes" getter="get_root_nodes" default="[  ]">
+		</member>
+		<member name="scene_name" type="String" setter="set_scene_name" getter="get_scene_name" default="&quot;&quot;">
+		</member>
+		<member name="use_named_skin_binds" type="bool" setter="set_use_named_skin_binds" getter="get_use_named_skin_binds" default="false">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 17 - 0
modules/gltf/doc_classes/GLTFTexture.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFTexture" inherits="Resource" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="src_image" type="int" setter="set_src_image" getter="get_src_image" default="0">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 58 - 0
modules/gltf/doc_classes/PackedSceneGLTF.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="PackedSceneGLTF" inherits="PackedScene" version="3.4">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="export_gltf">
+			<return type="int" enum="Error">
+			</return>
+			<argument index="0" name="node" type="Node">
+			</argument>
+			<argument index="1" name="path" type="String">
+			</argument>
+			<argument index="2" name="flags" type="int" default="0">
+			</argument>
+			<argument index="3" name="bake_fps" type="float" default="1000.0">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="import_gltf_scene">
+			<return type="Node">
+			</return>
+			<argument index="0" name="path" type="String">
+			</argument>
+			<argument index="1" name="flags" type="int" default="0">
+			</argument>
+			<argument index="2" name="bake_fps" type="float" default="1000.0">
+			</argument>
+			<argument index="3" name="state" type="GLTFState" default="null">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="pack_gltf">
+			<return type="void">
+			</return>
+			<argument index="0" name="path" type="String">
+			</argument>
+			<argument index="1" name="flags" type="int" default="0">
+			</argument>
+			<argument index="2" name="bake_fps" type="float" default="1000.0">
+			</argument>
+			<argument index="3" name="state" type="GLTFState" default="null">
+			</argument>
+			<description>
+			</description>
+		</method>
+	</methods>
+	<members>
+		<member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" override="true" default="{&quot;conn_count&quot;: 0,&quot;conns&quot;: PoolIntArray(  ),&quot;editable_instances&quot;: [  ],&quot;names&quot;: PoolStringArray(  ),&quot;node_count&quot;: 0,&quot;node_paths&quot;: [  ],&quot;nodes&quot;: PoolIntArray(  ),&quot;variants&quot;: [  ],&quot;version&quot;: 2}" />
+	</members>
+	<constants>
+	</constants>
+</class>

+ 95 - 0
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<String> 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();
+}

+ 55 - 0
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<PackedSceneGLTF> 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

+ 187 - 0
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<String> *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<String> *r_missing_deps,
+		Error *r_err) {
+	Ref<PackedSceneGLTF> importer;
+	importer.instance();
+	return importer->import_scene(p_path, p_flags, p_bake_fps, r_missing_deps, r_err, Ref<GLTFState>());
+}
+
+Ref<Animation> EditorSceneImporterGLTF::import_animation(const String &p_path,
+		uint32_t p_flags,
+		int p_bake_fps) {
+	return Ref<Animation>();
+}
+
+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<GLTFState>()));
+	ClassDB::bind_method(D_METHOD("import_gltf_scene", "path", "flags", "bake_fps", "state"),
+			&PackedSceneGLTF::import_gltf_scene, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref<GLTFState>()));
+}
+Node *PackedSceneGLTF::import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref<GLTFState> r_state) {
+	Error err = FAILED;
+	List<String> 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<String> *r_missing_deps,
+		Error *r_err,
+		Ref<GLTFState> r_state) {
+	if (r_state == Ref<GLTFState>()) {
+		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<GLTFDocument> 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<Spatial>(root);
+}
+
+void PackedSceneGLTF::pack_gltf(String p_path, int32_t p_flags,
+		real_t p_bake_fps, Ref<GLTFState> r_state) {
+	Error err = FAILED;
+	List<String> 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<String> *r_missing_deps,
+		Error *r_err) {
+	Error err = FAILED;
+	if (r_err) {
+		*r_err = err;
+	}
+	Ref<GLTFDocument> gltf_document;
+	gltf_document.instance();
+	Ref<GLTFState> state;
+	state.instance();
+	err = gltf_document->serialize(state, p_node, p_path);
+	if (r_err) {
+		*r_err = err;
+	}
+}
+
+void PackedSceneGLTF::_build_parent_hierachy(Ref<GLTFState> 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<String> deps;
+	Error err;
+	String path = p_path;
+	int32_t flags = p_flags;
+	real_t baked_fps = p_bake_fps;
+	Ref<PackedSceneGLTF> 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;
+}

+ 96 - 0
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<String> *r_extensions) const;
+	virtual Node *import_scene(const String &p_path, uint32_t p_flags,
+			int p_bake_fps,
+			List<String> *r_missing_deps = NULL,
+			Error *r_err = NULL);
+	virtual Ref<Animation> 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<String> *r_missing_deps, Error *r_err = NULL);
+	virtual void _build_parent_hierachy(Ref<GLTFState> 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<String> *r_missing_deps,
+			Error *r_err,
+			Ref<GLTFState> r_state);
+	virtual Node *import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref<GLTFState> r_state = Ref<GLTFState>());
+	virtual void pack_gltf(String p_path, int32_t p_flags = 0,
+			real_t p_bake_fps = 1000.0f, Ref<GLTFState> r_state = Ref<GLTFState>());
+};
+#endif // EDITOR_SCENE_IMPORTER_GLTF_H

+ 189 - 0
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<real_t>
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_REAL_ARRAY, "max"), "set_max", "get_max"); // Vector<real_t>
+	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<float> GLTFAccessor::get_min() {
+	return min;
+}
+
+void GLTFAccessor::set_min(PoolVector<float> p_min) {
+	min = p_min;
+}
+
+PoolVector<float> GLTFAccessor::get_max() {
+	return max;
+}
+
+void GLTFAccessor::set_max(PoolVector<float> 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;
+}

+ 104 - 0
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<float> min;
+	PoolVector<float> 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<float> get_min();
+	void set_min(PoolVector<float> p_min);
+
+	PoolVector<float> get_max();
+	void set_max(PoolVector<float> 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

+ 53 - 0
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<int, GLTFAnimation::Track> &GLTFAnimation::get_tracks() {
+	return tracks;
+}
+
+GLTFAnimation::GLTFAnimation() {
+}

+ 74 - 0
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 <class T>
+	struct Channel {
+		Interpolation interpolation;
+		Vector<float> times;
+		Vector<T> values;
+	};
+
+	struct Track {
+		Channel<Vector3> translation_track;
+		Channel<Quat> rotation_track;
+		Channel<Vector3> scale_track;
+		Vector<Channel<float>> weight_tracks;
+	};
+
+public:
+	bool get_loop() const;
+	void set_loop(bool p_val);
+	Map<int, GLTFAnimation::Track> &get_tracks();
+	GLTFAnimation();
+
+private:
+	bool loop = false;
+	Map<int, Track> tracks;
+};
+#endif // GLTF_ANIMATION_H

+ 90 - 0
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;
+}

+ 68 - 0
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

+ 47 - 0
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
+}

+ 58 - 0
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

+ 6735 - 0
modules/gltf/gltf_document.cpp

@@ -0,0 +1,6735 @@
+/*************************************************************************/
+/*  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 <stdio.h>
+#include <stdlib.h>
+
+#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 <limits>
+
+Error GLTFDocument::serialize(Ref<GLTFState> 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<uint8_t>());
+	}
+
+	/* 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<GLTFState> 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<GLTFState> 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<GLTFState> state) {
+	Error err;
+	FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err);
+	if (!f) {
+		return err;
+	}
+
+	Vector<uint8_t> 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<GLTFState> 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<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<uint8_t> 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<real_t> _xform_to_array(const Transform p_transform) {
+	Vector<real_t> 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<GLTFState> state) {
+	Array nodes;
+	for (int i = 0; i < state->nodes.size(); i++) {
+		Dictionary node;
+		Ref<GLTFNode> 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<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 GLTFDocument::_legacy_validate_node_name(const String &p_name) {
+	String invalid_character = ". : @ / \"";
+	String name = p_name;
+	Vector<String> 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<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 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<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 GLTFDocument::_sanitize_bone_name(Ref<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 GLTFDocument::_gen_unique_bone_name(Ref<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 GLTFDocument::_parse_scenes(Ref<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") && !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<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++) {
+		Ref<GLTFNode> 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<GLTFState> state) {
+	state->root_nodes.clear();
+	for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); ++node_i) {
+		Ref<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<uint8_t> _parse_base64_uri(const String &uri) {
+	int start = uri.find(",");
+	ERR_FAIL_COND_V(start == -1, Vector<uint8_t>());
+
+	CharString substr = uri.right(start + 1).ascii();
+
+	int strlen = substr.length();
+
+	Vector<uint8_t> 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<uint8_t>());
+
+	buf.resize(len);
+
+	return buf;
+}
+Error GLTFDocument::_encode_buffer_glb(Ref<GLTFState> 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<uint8_t> 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<uint8_t> 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<GLTFState> 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<uint8_t> 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<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<uint8_t> 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<GLTFState> state) {
+	Array buffers;
+	for (GLTFBufferViewIndex i = 0; i < state->buffer_views.size(); i++) {
+		Dictionary d;
+
+		Ref<GLTFBufferView> 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<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];
+
+		Ref<GLTFBufferView> 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<GLTFState> state) {
+	Array accessors;
+	for (GLTFAccessorIndex i = 0; i < state->accessors.size(); i++) {
+		Dictionary d;
+
+		Ref<GLTFAccessor> 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<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];
+
+		Ref<GLTFAccessor> 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<float>::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<float>::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 "<Error>";
+}
+
+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<GLTFState> 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<GLTFBufferView> bv;
+	bv.instance();
+	const uint32_t offset = bv->byte_offset = byte_offset;
+	Vector<uint8_t> &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<int8_t> 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<uint8_t> 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<int16_t> 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<uint16_t> 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<int> 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<float> 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<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 Ref<GLTFBufferView> 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<uint8_t> 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<double> GLTFDocument::_decode_accessor(Ref<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<double>());
+
+	const Ref<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<double>());
+	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<double> 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<double>());
+
+		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<double>();
+		}
+	} 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<double> 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<double>();
+		}
+
+		Vector<double> 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<double>();
+		}
+
+		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<GLTFState> state, const Vector<int32_t> 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<double> attribs;
+	attribs.resize(ret_size);
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<int> 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<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<float> 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<GLTFState> state, const Vector<Vector2> 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<double> attribs;
+	attribs.resize(ret_size);
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<GLTFState> state, const Vector<Color> p_attribs, const bool p_for_vertex) {
+	if (p_attribs.size() == 0) {
+		return -1;
+	}
+
+	const int ret_size = p_attribs.size() * 4;
+	Vector<double> attribs;
+	attribs.resize(ret_size);
+
+	const int element_count = 4;
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<double> &type_max, Vector<double> attribs, Vector<double> &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<GLTFState> state, const Vector<Color> p_attribs, const bool p_for_vertex) {
+	if (p_attribs.size() == 0) {
+		return -1;
+	}
+
+	const int ret_size = p_attribs.size() * 4;
+	Vector<double> attribs;
+	attribs.resize(ret_size);
+
+	const int element_count = 4;
+
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<GLTFState> state, const Vector<Color> 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<double> attribs;
+	attribs.resize(ret_size);
+
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<GLTFState> state, const Vector<Quat> 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<double> attribs;
+	attribs.resize(ret_size);
+
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<Vector2> 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<GLTFState> state, const Vector<real_t> 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<double> attribs;
+	attribs.resize(ret_size);
+
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<GLTFState> state, const Vector<Vector3> 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<double> attribs;
+	attribs.resize(ret_size);
+
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<GLTFState> state, const Vector<Transform> 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<double> attribs;
+	attribs.resize(ret_size);
+
+	Vector<double> type_max;
+	type_max.resize(element_count);
+	Vector<double> 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<GLTFAccessor> 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<float> max;
+	max.resize(type_max.size());
+	PoolVector<float>::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<float> min;
+	min.resize(type_min.size());
+	PoolVector<float>::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<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<Vector3> 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<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<Color> 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<Quat> GLTFDocument::_decode_accessor_as_quat(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<Quat> 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<Transform2D> GLTFDocument::_decode_accessor_as_xform2d(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<Transform2D> 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<Basis> GLTFDocument::_decode_accessor_as_basis(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<Basis> 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<Transform> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+	const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex);
+	Vector<Transform> 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<GLTFState> 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<ArrayMesh> 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<Vector3> a = array[Mesh::ARRAY_VERTEX];
+				ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA);
+				attributes["POSITION"] = _encode_accessor_as_vec3(state, a, true);
+			}
+			{
+				Vector<real_t> a = array[Mesh::ARRAY_TANGENT];
+				if (a.size()) {
+					const int ret_size = a.size() / 4;
+					Vector<Color> 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<Vector3> a = array[Mesh::ARRAY_NORMAL];
+				if (a.size()) {
+					const int ret_size = a.size();
+					Vector<Vector3> 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<Vector2> a = array[Mesh::ARRAY_TEX_UV];
+				if (a.size()) {
+					attributes["TEXCOORD_0"] = _encode_accessor_as_vec2(state, a, true);
+				}
+			}
+			{
+				Vector<Vector2> a = array[Mesh::ARRAY_TEX_UV2];
+				if (a.size()) {
+					attributes["TEXCOORD_1"] = _encode_accessor_as_vec2(state, a, true);
+				}
+			}
+			{
+				Vector<Color> a = array[Mesh::ARRAY_COLOR];
+				if (a.size()) {
+					attributes["COLOR_0"] = _encode_accessor_as_color(state, a, true);
+				}
+			}
+			Map<int, int> 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<Vector3> &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<Color> 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<Vector3> &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<Color> 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<Color> weights_0;
+					weights_0.resize(vertex_count);
+					Vector<Color> 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<int32_t> 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<Vector3> &vertices = array[Mesh::ARRAY_VERTEX];
+						Ref<SurfaceTool> st;
+						st.instance();
+						st->create_from_triangle_arrays(array);
+						st->index();
+						Vector<int32_t> 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<Vector3> varr = array_morph[Mesh::ARRAY_VERTEX];
+					Array mesh_arrays = import_mesh->surface_get_arrays(surface_i);
+					if (varr.size()) {
+						Vector<Vector3> 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<Vector3> narr = array_morph[Mesh::ARRAY_NORMAL];
+					if (varr.size()) {
+						t["NORMAL"] = _encode_accessor_as_vec3(state, narr, true);
+					}
+					Vector<real_t> tarr = array_morph[Mesh::ARRAY_TANGENT];
+					if (tarr.size()) {
+						const int ret_size = tarr.size() / 4;
+						Vector<Color> 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<SpatialMaterial> mat = import_mesh->surface_get_material(surface_i);
+			if (mat.is_valid()) {
+				Map<Ref<Material>, 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<GLTFState> 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<GLTFMesh> 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<ArrayMesh> 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<float> 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<int> 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<Vector3> &vertices = array[Mesh::ARRAY_VERTEX];
+				ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR);
+				Vector<int> 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<SurfaceTool> 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<Vector3> varr = _decode_accessor_as_vec3(state, t["POSITION"], true);
+						const Vector<Vector3> 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<Vector3> narr = _decode_accessor_as_vec3(state, t["NORMAL"], true);
+						const Vector<Vector3> 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<Vector3> tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true);
+						const Vector<float> src_tangents = array[Mesh::ARRAY_TANGENT];
+						ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR);
+
+						Vector<float> 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<SurfaceTool> 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<SpatialMaterial> mat;
+			if (p.has("material")) {
+				const int material = p["material"];
+				ERR_FAIL_INDEX_V(material, state->materials.size(), ERR_FILE_CORRUPT);
+				Ref<SpatialMaterial> 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<SpatialMaterial> 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<float> 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<GLTFState> 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> image = state->images[i]->get_data();
+		ERR_CONTINUE(image.is_null());
+
+		if (p_path.to_lower().ends_with("glb")) {
+			GLTFBufferViewIndex bvi;
+
+			Ref<GLTFBufferView> 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<uint8_t> buffer;
+			Ref<ImageTexture> 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<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<uint8_t> 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<Texture>()); // 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> 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<Texture>()); // 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<Texture>()); // 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<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<Image> 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<Texture>());
+			continue;
+		}
+
+		Ref<ImageTexture> 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<GLTFState> state) {
+	if (!state->textures.size()) {
+		return OK;
+	}
+
+	Array textures;
+	for (int32_t i = 0; i < state->textures.size(); i++) {
+		Dictionary d;
+		Ref<GLTFTexture> 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<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);
+
+		Ref<GLTFTexture> t;
+		t.instance();
+		t->set_src_image(d["source"]);
+		state->textures.push_back(t);
+	}
+
+	return OK;
+}
+
+GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture> p_texture) {
+	ERR_FAIL_COND_V(p_texture.is_null(), -1);
+	Ref<GLTFTexture> 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<Texture> GLTFDocument::_get_texture(Ref<GLTFState> state, const GLTFTextureIndex p_texture) {
+	ERR_FAIL_INDEX_V(p_texture, state->textures.size(), Ref<Texture>());
+	const GLTFImageIndex image = state->textures[p_texture]->get_src_image();
+
+	ERR_FAIL_INDEX_V(image, state->images.size(), Ref<Texture>());
+
+	return state->images[image];
+}
+
+Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
+	Array materials;
+	for (int32_t i = 0; i < state->materials.size(); i++) {
+		Dictionary d;
+
+		Ref<SpatialMaterial> 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<Texture> 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<Texture> roughness_texture = material->get_texture(SpatialMaterial::TEXTURE_ROUGHNESS);
+				SpatialMaterial::TextureChannel roughness_channel = material->get_roughness_texture_channel();
+				Ref<Texture> metallic_texture = material->get_texture(SpatialMaterial::TEXTURE_METALLIC);
+				SpatialMaterial::TextureChannel metalness_channel = material->get_metallic_texture_channel();
+				Ref<Texture> ao_texture = material->get_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION);
+				SpatialMaterial::TextureChannel ao_channel = material->get_ao_texture_channel();
+				Ref<ImageTexture> orm_texture;
+				orm_texture.instance();
+				Ref<Image> orm_image;
+				orm_image.instance();
+				int32_t height = 0;
+				int32_t width = 0;
+				Ref<Image> ao_image;
+				if (has_ao) {
+					height = ao_texture->get_height();
+					width = ao_texture->get_width();
+					ao_image = ao_texture->get_data();
+					Ref<ImageTexture> img_tex = ao_image;
+					if (img_tex.is_valid()) {
+						ao_image = img_tex->get_data();
+					}
+					if (ao_image->is_compressed()) {
+						ao_image->decompress();
+					}
+				}
+				Ref<Image> roughness_image;
+				if (has_roughness) {
+					height = roughness_texture->get_height();
+					width = roughness_texture->get_width();
+					roughness_image = roughness_texture->get_data();
+					Ref<ImageTexture> img_tex = roughness_image;
+					if (img_tex.is_valid()) {
+						roughness_image = img_tex->get_data();
+					}
+					if (roughness_image->is_compressed()) {
+						roughness_image->decompress();
+					}
+				}
+				Ref<Image> metallness_image;
+				if (has_metalness) {
+					height = metallic_texture->get_height();
+					width = metallic_texture->get_width();
+					metallness_image = metallic_texture->get_data();
+					Ref<ImageTexture> img_tex = metallness_image;
+					if (img_tex.is_valid()) {
+						metallness_image = img_tex->get_data();
+					}
+					if (metallness_image->is_compressed()) {
+						metallness_image->decompress();
+					}
+				}
+				Ref<Texture> 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<ImageTexture> tex;
+			tex.instance();
+			{
+				Ref<Texture> normal_texture = material->get_texture(SpatialMaterial::TEXTURE_NORMAL);
+				// Code for uncompressing RG normal maps
+				Ref<Image> img = normal_texture->get_data();
+				Ref<ImageTexture> 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<Texture> 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<Texture> 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<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<SpatialMaterial> 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<GLTFSpecGloss> spec_gloss;
+			spec_gloss.instance();
+			if (sgm.has("diffuseTexture")) {
+				const Dictionary &diffuse_texture_dict = sgm["diffuseTexture"];
+				if (diffuse_texture_dict.has("index")) {
+					Ref<Texture> 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<Texture> 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<Texture> 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<SpatialMaterial> 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<GLTFSpecGloss> r_spec_gloss, Ref<SpatialMaterial> p_material) {
+	if (r_spec_gloss->spec_gloss_img.is_null()) {
+		return;
+	}
+	if (r_spec_gloss->diffuse_img.is_null()) {
+		return;
+	}
+	Ref<Image> 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<ImageTexture> 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<ImageTexture> 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<GLTFState> state, const Vector<GLTFNodeIndex> &subset) {
+	int highest = -1;
+	GLTFNodeIndex best_node = -1;
+
+	for (int i = 0; i < subset.size(); ++i) {
+		const GLTFNodeIndex node_i = subset[i];
+		const Ref<GLTFNode> 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<GLTFState> state, Ref<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 GLTFDocument::_capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) {
+	DisjointSet<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFState> state, Ref<GLTFSkin> skin) {
+	_capture_nodes_for_multirooted_skin(state, skin);
+
+	// Grab all nodes that lay in between skin joints/nodes
+	DisjointSet<GLTFNodeIndex> disjoint_set;
+
+	Vector<GLTFNodeIndex> 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<GLTFNodeIndex> out_owners;
+	disjoint_set.get_representatives(out_owners);
+
+	Vector<GLTFNodeIndex> out_roots;
+
+	for (int i = 0; i < out_owners.size(); ++i) {
+		Vector<GLTFNodeIndex> 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<GLTFState> state, Ref<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<GLTFNodeIndex> disjoint_set;
+
+	Vector<GLTFNodeIndex> 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<GLTFNodeIndex> out_owners;
+	disjoint_set.get_representatives(out_owners);
+
+	Vector<GLTFNodeIndex> out_roots;
+
+	for (int i = 0; i < out_owners.size(); ++i) {
+		Vector<GLTFNodeIndex> 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<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];
+
+		Ref<GLTFSkin> 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<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 GLTFDocument::_determine_skeletons(Ref<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<GLTFNodeIndex> skeleton_sets;
+
+	for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) {
+		const Ref<GLTFSkin> skin = state->skins[skin_i];
+
+		Vector<GLTFNodeIndex> 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<GLTFNodeIndex> groups_representatives;
+		skeleton_sets.get_representatives(groups_representatives);
+
+		Vector<GLTFNodeIndex> highest_group_members;
+		Vector<Vector<GLTFNodeIndex>> groups;
+		for (int i = 0; i < groups_representatives.size(); ++i) {
+			Vector<GLTFNodeIndex> 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<GLTFNodeIndex> &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<GLTFNodeIndex> 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<GLTFSkeleton> skeleton;
+		skeleton.instance();
+
+		Vector<GLTFNodeIndex> skeleton_nodes;
+		skeleton_sets.get_members(skeleton_nodes, skeleton_owner);
+
+		for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) {
+			Ref<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<GLTFNodeIndex> 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<GLTFSkeleton> skeleton = state->skeletons.write[skel_i];
+
+		for (int i = 0; i < skeleton->joints.size(); ++i) {
+			const GLTFNodeIndex node_i = skeleton->joints[i];
+			Ref<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 GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton, const Vector<GLTFNodeIndex> &non_joints) {
+	DisjointSet<GLTFNodeIndex> 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<GLTFNodeIndex> 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<GLTFNodeIndex> subtree_nodes;
+		subtree_set.get_members(subtree_nodes, subtree_root);
+
+		for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) {
+			Ref<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 GLTFDocument::_determine_skeleton_roots(Ref<GLTFState> state, const GLTFSkeletonIndex skel_i) {
+	DisjointSet<GLTFNodeIndex> disjoint_set;
+
+	for (GLTFNodeIndex i = 0; i < state->nodes.size(); ++i) {
+		const Ref<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);
+		}
+	}
+
+	Ref<GLTFSkeleton> skeleton = state->skeletons.write[skel_i];
+
+	Vector<GLTFNodeIndex> owners;
+	disjoint_set.get_representatives(owners);
+
+	Vector<GLTFNodeIndex> roots;
+
+	for (int i = 0; i < owners.size(); ++i) {
+		Vector<GLTFNodeIndex> 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<GLTFNodeIndex> roots_array;
+	roots_array.resize(roots.size());
+	PoolVector<GLTFNodeIndex>::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<GLTFState> state) {
+	for (GLTFSkeletonIndex skel_i = 0; skel_i < state->skeletons.size(); ++skel_i) {
+		Ref<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<GLTFNodeIndex> 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<GLTFNode> node = state->nodes[node_i];
+			ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED);
+
+			{ // Add all child nodes to the stack (deterministically)
+				Vector<GLTFNodeIndex> 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<GLTFState> state) {
+	for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) {
+		Ref<GLTFSkin> skin = state->skins.write[skin_i];
+
+		Ref<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 Ref<GLTFNode> 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<GLTFState> state) {
+	_remove_duplicate_skins(state);
+	return OK;
+}
+
+Error GLTFDocument::_create_skins(Ref<GLTFState> state) {
+	for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) {
+		Ref<GLTFSkin> gltf_skin = state->skins.write[skin_i];
+
+		Ref<Skin> 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> 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> skin_a, const Ref<Skin> 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<GLTFState> state) {
+	for (int i = 0; i < state->skins.size(); ++i) {
+		for (int j = i + 1; j < state->skins.size(); ++j) {
+			const Ref<Skin> skin_i = state->skins[i]->godot_skin;
+			const Ref<Skin> 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<GLTFState> state) {
+	Array lights;
+	for (GLTFLightIndex i = 0; i < state->lights.size(); i++) {
+		Dictionary d;
+		Ref<GLTFLight> 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<GLTFState> state) {
+	Array cameras;
+	cameras.resize(state->cameras.size());
+	for (GLTFCameraIndex i = 0; i < state->cameras.size(); i++) {
+		Dictionary d;
+
+		Ref<GLTFCamera> 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<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];
+
+		Ref<GLTFLight> 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<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];
+
+		Ref<GLTFCamera> 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<GLTFState> state) {
+	if (!state->animation_players.size()) {
+		return OK;
+	}
+	for (int32_t player_i = 0; player_i < state->animation_players.size(); player_i++) {
+		List<StringName> 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<GLTFAnimation> 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<int, GLTFAnimation::Track>::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<real_t> times = Variant(track.translation_track.times);
+				s["input"] = _encode_accessor_as_floats(state, times, false);
+				Vector<Vector3> 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<real_t> times = Variant(track.rotation_track.times);
+				s["input"] = _encode_accessor_as_floats(state, times, false);
+				Vector<Quat> 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<real_t> times = Variant(track.scale_track.times);
+				s["input"] = _encode_accessor_as_floats(state, times, false);
+				Vector<Vector3> 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<real_t> times;
+				Vector<real_t> 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<float> 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<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];
+
+		Ref<GLTFAnimation> 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<float> times = _decode_accessor_as_floats(state, input, false);
+			if (path == "translation") {
+				const Vector<Vector3> 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<Quat> 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<Vector3> 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<float> weights = _decode_accessor_as_floats(state, output, false);
+
+				ERR_FAIL_INDEX_V(state->nodes[node]->mesh, state->meshes.size(), ERR_PARSE_ERROR);
+				Ref<GLTFMesh> 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<float> cf;
+					cf.interpolation = interp;
+					cf.times = Variant(times);
+					Vector<float> 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<GLTFState> state) {
+	for (int i = 0; i < state->nodes.size(); i++) {
+		Ref<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->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<GLTFState> state, Skeleton *skeleton, const GLTFNodeIndex node_index, const GLTFNodeIndex bone_index) {
+	Ref<GLTFNode> gltf_node = state->nodes[node_index];
+	Ref<GLTFNode> bone_node = state->nodes[bone_index];
+
+	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<GLTFState> state, MeshInstance *p_mesh_instance) {
+	ERR_FAIL_NULL_V(p_mesh_instance, -1);
+	if (p_mesh_instance->get_mesh().is_null()) {
+		return -1;
+	}
+	Ref<ArrayMesh> import_mesh;
+	import_mesh.instance();
+	Ref<Mesh> godot_mesh = p_mesh_instance->get_mesh();
+	if (godot_mesh.is_null()) {
+		return -1;
+	}
+	Vector<float> blend_weights;
+	Vector<String> 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<Material> mat = godot_mesh->surface_get_material(surface_i);
+		Ref<ArrayMesh> 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<GLTFMesh> 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<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) {
+	Ref<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->get_name());
+
+	Ref<GLTFMesh> mesh = state->meshes.write[gltf_node->mesh];
+	if (mesh.is_null()) {
+		return mi;
+	}
+	Ref<ArrayMesh> 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;
+}
+
+Spatial *GLTFDocument::_generate_light(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) {
+	Ref<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->get_name());
+
+	Ref<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 *GLTFDocument::_generate_camera(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) {
+	Ref<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->get_name());
+
+	Ref<GLTFCamera> 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<GLTFState> state, Camera *p_camera) {
+	print_verbose("glTF: Converting camera: " + p_camera->get_name());
+
+	Ref<GLTFCamera> 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<GLTFState> state, Light *p_light) {
+	print_verbose("glTF: Converting light: " + p_light->get_name());
+
+	Ref<GLTFLight> l;
+	l.instance();
+	l->color = p_light->get_color();
+	if (cast_to<DirectionalLight>(p_light)) {
+		l->type = "directional";
+		DirectionalLight *light = cast_to<DirectionalLight>(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<OmniLight>(p_light)) {
+		l->type = "point";
+		OmniLight *light = cast_to<OmniLight>(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<SpotLight>(p_light)) {
+		l->type = "spot";
+		SpotLight *light = cast_to<SpotLight>(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<GLTFState> state, Skeleton *p_skeleton) {
+	print_verbose("glTF: Converting skeleton: " + p_skeleton->get_name());
+	Ref<GLTFSkeleton> 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<GLTFState> state, Spatial *p_spatial, Ref<GLTFNode> 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<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) {
+	Ref<GLTFNode> 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<GLTFState> 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<GLTFNode> gltf_node;
+	gltf_node.instance();
+	gltf_node->set_name(_gen_unique_name(state, p_current->get_name()));
+	if (cast_to<Spatial>(p_current)) {
+		Spatial *spatial = cast_to<Spatial>(p_current);
+		_convert_spatial(state, spatial, gltf_node);
+	}
+	if (cast_to<MeshInstance>(p_current)) {
+		Spatial *spatial = cast_to<Spatial>(p_current);
+		_convert_mesh_to_gltf(p_current, state, spatial, gltf_node);
+	} else if (cast_to<BoneAttachment>(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<Skeleton>(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<MultiMeshInstance>(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<CSGShape>(p_current)) {
+		if (p_current->get_parent() && cast_to<CSGShape>(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<GridMap>(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<Camera>(p_current)) {
+		Camera *camera = Object::cast_to<Camera>(p_current);
+		_convert_camera_to_gltf(camera, state, camera, gltf_node);
+	} else if (cast_to<Light>(p_current)) {
+		Light *light = Object::cast_to<Light>(p_current);
+		_convert_light_to_gltf(light, state, light, gltf_node);
+	} else if (cast_to<AnimationPlayer>(p_current)) {
+		AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(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<GLTFNode> gltf_node, Ref<GLTFState> state) {
+	CSGShape *csg = Object::cast_to<CSGShape>(p_current);
+	csg->call("_update_shape");
+	Array meshes = csg->get_meshes();
+	if (meshes.size() != 2) {
+		return;
+	}
+	Ref<Material> mat;
+	if (csg->get_material_override().is_valid()) {
+		mat = csg->get_material_override();
+	}
+	Ref<GLTFMesh> gltf_mesh;
+	gltf_mesh.instance();
+	Ref<ArrayMesh> import_mesh;
+	import_mesh.instance();
+	Ref<ArrayMesh> 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<GLTFState> state, Node *p_scene_parent, GLTFNodeIndex current_node_i,
+		GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, Ref<GLTFNode> 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<GLTFState> state, const GLTFNodeIndex &p_gltf_current, const GLTFNodeIndex &p_gltf_root_index, Ref<GLTFNode> 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<Spatial>(p_node);
+	Node2D *node_2d = Object::cast_to<Node2D>(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<GLTFState> state, Spatial *spatial, Ref<GLTFNode> 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<GLTFState> state, Spatial *spatial, Ref<GLTFNode> 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<GLTFNode> gltf_node, Ref<GLTFState> state, Node *p_root_node) {
+	GridMap *grid_map = Object::cast_to<GridMap>(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<GLTFMesh> 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<GLTFNode> gltf_node, Ref<GLTFState> state, Node *p_root_node) {
+	MultiMeshInstance *multi_mesh_instance = Object::cast_to<MultiMeshInstance>(p_scene_parent);
+	ERR_FAIL_COND(!multi_mesh_instance);
+	Ref<MultiMesh> 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<ArrayMesh> mm = multi_mesh->get_mesh();
+			if (mm.is_valid()) {
+				Ref<ArrayMesh> 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<GLTFMesh> 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<GLTFState> state, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref<GLTFNode> gltf_node, Node *p_root_node) {
+	Skeleton *skeleton = Object::cast_to<Skeleton>(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<GLTFState> state, Ref<GLTFNode> gltf_node, bool &retflag) {
+	retflag = true;
+	BoneAttachment *bone_attachment = Object::cast_to<BoneAttachment>(p_scene_parent);
+	if (bone_attachment) {
+		Node *node = bone_attachment->get_parent();
+		while (node) {
+			Skeleton *bone_attachment_skeleton = Object::cast_to<Skeleton>(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<GLTFState> state, Spatial *spatial, Ref<GLTFNode> gltf_node) {
+	MeshInstance *mi = Object::cast_to<MeshInstance>(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<GLTFState> state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) {
+	Ref<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<Skeleton>(scene_parent);
+
+	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 (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<GLTFState> state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index) {
+	Ref<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<Skeleton>(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);
+
+			// 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);
+		}
+
+		scene_parent->add_child(current_node);
+		if (current_node != scene_root) {
+			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->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, active_skeleton, scene_root, gltf_node->children[i]);
+	}
+}
+
+template <class T>
+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> {
+	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 <class T>
+T GLTFDocument::_interpolate_track(const Vector<float> &p_times, const Vector<T> &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<T> 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<GLTFState> state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) {
+	Ref<GLTFAnimation> 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;
+	animation.instance();
+	animation->set_name(name);
+
+	if (anim->get_loop()) {
+		animation->set_loop(true);
+	}
+
+	float length = 0.0;
+
+	for (Map<int, GLTFAnimation::Track>::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: 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();
+
+		const Ref<GLTFNode> gltf_node = state->nodes[track_i->key()];
+
+		Node *root = ap->get_parent();
+		ERR_FAIL_COND(root == nullptr);
+		Map<GLTFNodeIndex, Node *>::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();
+			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 = 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, transform_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<Vector3>(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation);
+				}
+
+				if (track.rotation_track.times.size()) {
+					rot = _interpolate_track<Quat>(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation);
+				}
+
+				if (track.scale_track.times.size()) {
+					scale = _interpolate_track<Vector3>(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<GLTFMesh> 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<float>(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<GLTFState> state) {
+	for (GLTFNodeIndex mi_node_i = 0; mi_node_i < state->nodes.size(); ++mi_node_i) {
+		Ref<GLTFNode> 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<GLTFNodeIndex, Node *>::Element *mi_element = state->scene_nodes.find(mi_node_i);
+		if (!mi_element) {
+			continue;
+		}
+		MeshInstance *mi = Object::cast_to<MeshInstance>(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<Skeleton>(mi->get_node(mi->get_skeleton_path()));
+		if (!skeleton) {
+			continue;
+		}
+		if (!skeleton->get_bone_count()) {
+			continue;
+		}
+		Ref<Skin> skin = mi->get_skin();
+		if (skin.is_null()) {
+			skin = skeleton->register_skin(nullptr)->get_skin();
+		}
+		Ref<GLTFSkin> 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<GLTFSkin> 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<GLTFSkeleton> 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<GLTFNode> 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<GLTFNodeIndex, Node *>::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<GLTFNode> 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<GLTFNodeIndex, Node *>::Element *E = state->scene_nodes.front(); E; E = E->next()) {
+					if (E->get() == node_parent) {
+						GLTFNodeIndex gltf_node_i = E->key();
+						Ref<GLTFNode> 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<GLTFState> state, Node *scene_root) {
+	for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); ++node_i) {
+		Ref<GLTFNode> node = state->nodes[node_i];
+
+		if (node->skin >= 0 && node->mesh >= 0) {
+			const GLTFSkinIndex skin_i = node->skin;
+
+			Map<GLTFNodeIndex, Node *>::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<MeshInstance>(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<GLTFSkeleton> 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<GLTFState> state, GLTFAnimation::Track p_track, Ref<Animation> 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<float> 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<float> 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<float> 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<GLTFState> state, AnimationPlayer *ap, String p_animation_track_name) {
+	Ref<Animation> animation = ap->get_animation(p_animation_track_name);
+	Ref<GLTFAnimation> 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<String> 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<GLTFNodeIndex, Node *>::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<int, GLTFAnimation::Track>::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<String> 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<GLTFNodeIndex, Node *>::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<int, GLTFAnimation::Track>::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<String> 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<GLTFNodeIndex, Node *>::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<int, GLTFAnimation::Track>::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<String> 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<GLTFNodeIndex, Node *>::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<String> 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<GLTFNodeIndex, Node *>::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<MeshInstance>(node);
+					if (!mi) {
+						continue;
+					}
+					Ref<ArrayMesh> 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> 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<int, GLTFAnimation::Track>::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<float> 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<String> 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<Skeleton>(godot_node)) {
+					skeleton = state->skeletons[skeleton_i]->godot_skeleton;
+					skeleton_gltf_i = skeleton_i;
+					ERR_CONTINUE(!skeleton);
+					Ref<GLTFSkeleton> 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<int, GLTFAnimation::Track>::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<GLTFNodeIndex, Node *>::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<int, GLTFAnimation::Track>::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<GLTFState> 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<SpatialMaterial> p_material) {
+	Dictionary extension;
+	Ref<SpatialMaterial> 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<SpatialMaterial> 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<GLTFState> 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<GLTFState> 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;
+}

+ 437 - 0
modules/gltf/gltf_document.h

@@ -0,0 +1,437 @@
+/*************************************************************************/
+/*  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 <class T>
+	static Array to_array(const Vector<T> &p_inp) {
+		Array ret;
+		for (int i = 0; i < p_inp.size(); i++) {
+			ret.push_back(p_inp[i]);
+		}
+		return ret;
+	}
+
+	template <class T>
+	static Array to_array(const Set<T> &p_inp) {
+		Array ret;
+		typename Set<T>::Element *elem = p_inp.front();
+		while (elem) {
+			ret.push_back(elem->get());
+			elem = elem->next();
+		}
+		return ret;
+	}
+
+	template <class T>
+	static void set_from_array(Vector<T> &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 <class T>
+	static void set_from_array(Set<T> &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 <class K, class V>
+	static Dictionary to_dict(const Map<K, V> &p_inp) {
+		Dictionary ret;
+		for (typename Map<K, V>::Element *E = p_inp.front(); E; E = E->next()) {
+			ret[E->key()] = E->value();
+		}
+		return ret;
+	}
+
+	template <class K, class V>
+	static void set_from_dict(Map<K, V> &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<GLTFState> state);
+	Error _parse_nodes(Ref<GLTFState> state);
+	String _get_type_name(const GLTFType p_component);
+	String _get_accessor_type_name(const GLTFDocument::GLTFType p_type);
+	String _gen_unique_name(Ref<GLTFState> state, const String &p_name);
+	String _sanitize_animation_name(const String &name);
+	String _gen_unique_animation_name(Ref<GLTFState> state, const String &p_name);
+	String _sanitize_bone_name(Ref<GLTFState> state, const String &name);
+	String _gen_unique_bone_name(Ref<GLTFState> state,
+			const GLTFSkeletonIndex skel_i,
+			const String &p_name);
+	GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture> p_texture);
+	Ref<Texture> _get_texture(Ref<GLTFState> state,
+			const GLTFTextureIndex p_texture);
+	Error _parse_json(const String &p_path, Ref<GLTFState> state);
+	Error _parse_glb(const String &p_path, Ref<GLTFState> state);
+	void _compute_node_heights(Ref<GLTFState> state);
+	Error _parse_buffers(Ref<GLTFState> state, const String &p_base_path);
+	Error _parse_buffer_views(Ref<GLTFState> state);
+	GLTFType _get_type_from_str(const String &p_string);
+	Error _parse_accessors(Ref<GLTFState> state);
+	Error _decode_buffer_view(Ref<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<double> _decode_accessor(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<float> _decode_accessor_as_floats(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<int> _decode_accessor_as_ints(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<Vector2> _decode_accessor_as_vec2(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<Vector3> _decode_accessor_as_vec3(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<Color> _decode_accessor_as_color(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<Quat> _decode_accessor_as_quat(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<Transform2D> _decode_accessor_as_xform2d(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<Basis> _decode_accessor_as_basis(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Vector<Transform> _decode_accessor_as_xform(Ref<GLTFState> state,
+			const GLTFAccessorIndex p_accessor,
+			const bool p_for_vertex);
+	Error _parse_meshes(Ref<GLTFState> state);
+	Error _serialize_textures(Ref<GLTFState> state);
+	Error _serialize_images(Ref<GLTFState> state, const String &p_path);
+	Error _serialize_lights(Ref<GLTFState> state);
+	Error _parse_images(Ref<GLTFState> state, const String &p_base_path);
+	Error _parse_textures(Ref<GLTFState> state);
+	Error _parse_materials(Ref<GLTFState> state);
+	void _set_texture_transform_uv1(const Dictionary &d, Ref<SpatialMaterial> material);
+	void spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss,
+			Ref<SpatialMaterial> 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<GLTFState> state,
+			const Vector<GLTFNodeIndex> &subset);
+	bool _capture_nodes_in_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin,
+			const GLTFNodeIndex node_index);
+	void _capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin);
+	Error _expand_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin);
+	Error _verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin);
+	Error _parse_skins(Ref<GLTFState> state);
+	Error _determine_skeletons(Ref<GLTFState> state);
+	Error _reparent_non_joint_skeleton_subtrees(
+			Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton,
+			const Vector<GLTFNodeIndex> &non_joints);
+	Error _reparent_to_fake_joint(Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton,
+			const GLTFNodeIndex node_index);
+	Error _determine_skeleton_roots(Ref<GLTFState> state,
+			const GLTFSkeletonIndex skel_i);
+	Error _create_skeletons(Ref<GLTFState> state);
+	Error _map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFState> state);
+	Error _serialize_skins(Ref<GLTFState> state);
+	Error _create_skins(Ref<GLTFState> state);
+	bool _skins_are_same(const Ref<Skin> skin_a, const Ref<Skin> skin_b);
+	void _remove_duplicate_skins(Ref<GLTFState> state);
+	Error _serialize_cameras(Ref<GLTFState> state);
+	Error _parse_cameras(Ref<GLTFState> state);
+	Error _parse_lights(Ref<GLTFState> state);
+	Error _parse_animations(Ref<GLTFState> state);
+	Error _serialize_animations(Ref<GLTFState> state);
+	BoneAttachment *_generate_bone_attachment(Ref<GLTFState> state,
+			Skeleton *skeleton,
+			const GLTFNodeIndex node_index,
+			const GLTFNodeIndex bone_index);
+	Spatial *_generate_mesh_instance(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index);
+	Camera *_generate_camera(Ref<GLTFState> state, Node *scene_parent,
+			const GLTFNodeIndex node_index);
+	Spatial *_generate_light(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index);
+	Spatial *_generate_spatial(Ref<GLTFState> state, Node *scene_parent,
+			const GLTFNodeIndex node_index);
+	void _assign_scene_names(Ref<GLTFState> state);
+	template <class T>
+	T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values,
+			const float p_time,
+			const GLTFAnimation::Interpolation p_interp);
+	GLTFAccessorIndex _encode_accessor_as_quats(Ref<GLTFState> state,
+			const Vector<Quat> p_attribs,
+			const bool p_for_vertex);
+	GLTFAccessorIndex _encode_accessor_as_weights(Ref<GLTFState> state,
+			const Vector<Color> p_attribs,
+			const bool p_for_vertex);
+	GLTFAccessorIndex _encode_accessor_as_joints(Ref<GLTFState> state,
+			const Vector<Color> p_attribs,
+			const bool p_for_vertex);
+	GLTFAccessorIndex _encode_accessor_as_floats(Ref<GLTFState> state,
+			const Vector<real_t> p_attribs,
+			const bool p_for_vertex);
+	GLTFAccessorIndex _encode_accessor_as_vec2(Ref<GLTFState> state,
+			const Vector<Vector2> p_attribs,
+			const bool p_for_vertex);
+
+	void _calc_accessor_vec2_min_max(int i, const int element_count, Vector<double> &type_max, Vector2 attribs, Vector<double> &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<GLTFState> state,
+			const Vector<Vector3> p_attribs,
+			const bool p_for_vertex);
+	GLTFAccessorIndex _encode_accessor_as_color(Ref<GLTFState> state,
+			const Vector<Color> p_attribs,
+			const bool p_for_vertex);
+
+	void _calc_accessor_min_max(int p_i, const int p_element_count, Vector<double> &p_type_max, Vector<double> p_attribs, Vector<double> &p_type_min);
+
+	GLTFAccessorIndex _encode_accessor_as_ints(Ref<GLTFState> state,
+			const Vector<int32_t> p_attribs,
+			const bool p_for_vertex);
+	GLTFAccessorIndex _encode_accessor_as_xform(Ref<GLTFState> state,
+			const Vector<Transform> p_attribs,
+			const bool p_for_vertex);
+	Error _encode_buffer_view(Ref<GLTFState> 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<GLTFState> state);
+	Error _encode_buffer_views(Ref<GLTFState> state);
+	Error _serialize_materials(Ref<GLTFState> state);
+	Error _serialize_meshes(Ref<GLTFState> state);
+	Error _serialize_nodes(Ref<GLTFState> state);
+	Error _serialize_scenes(Ref<GLTFState> state);
+	String interpolation_to_string(const GLTFAnimation::Interpolation p_interp);
+	GLTFAnimation::Track _convert_animation_track(Ref<GLTFState> state,
+			GLTFAnimation::Track p_track,
+			Ref<Animation> p_animation, Transform p_bone_rest,
+			int32_t p_track_i,
+			GLTFNodeIndex p_node_i);
+	Error _encode_buffer_bins(Ref<GLTFState> state, const String &p_path);
+	Error _encode_buffer_glb(Ref<GLTFState> state, const String &p_path);
+	Error _serialize_bone_attachment(Ref<GLTFState> state);
+	Dictionary _serialize_texture_transform_uv1(Ref<SpatialMaterial> p_material);
+	Dictionary _serialize_texture_transform_uv2(Ref<SpatialMaterial> p_material);
+	Error _serialize_version(Ref<GLTFState> state);
+	Error _serialize_file(Ref<GLTFState> state, const String p_path);
+	Error _serialize_extensions(Ref<GLTFState> 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<GLTFState> state, const String &p_name);
+	String _legacy_validate_node_name(const String &p_name);
+
+	void _process_mesh_instances(Ref<GLTFState> state, Node *scene_root);
+	void _generate_scene_node(Ref<GLTFState> state, Node *scene_parent,
+			Spatial *scene_root,
+			const GLTFNodeIndex node_index);
+	void _generate_skeleton_bone_node(Ref<GLTFState> state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index);
+	void _import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
+			const GLTFAnimationIndex index, const int bake_fps);
+	GLTFMeshIndex _convert_mesh_instance(Ref<GLTFState> state,
+			MeshInstance *p_mesh_instance);
+	void _convert_mesh_instances(Ref<GLTFState> state);
+	GLTFCameraIndex _convert_camera(Ref<GLTFState> state, Camera *p_camera);
+	void _convert_light_to_gltf(Light *light, Ref<GLTFState> state, Spatial *spatial, Ref<GLTFNode> gltf_node);
+	GLTFLightIndex _convert_light(Ref<GLTFState> state, Light *p_light);
+	GLTFSkeletonIndex _convert_skeleton(Ref<GLTFState> state, Skeleton *p_skeleton);
+	void _convert_spatial(Ref<GLTFState> state, Spatial *p_spatial, Ref<GLTFNode> p_node);
+	void _convert_scene_node(Ref<GLTFState> 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<GLTFNode> gltf_node, Ref<GLTFState> state);
+#endif // MODULE_CSG_ENABLED
+
+	void _create_gltf_node(Ref<GLTFState> state,
+			Node *p_scene_parent,
+			GLTFNodeIndex current_node_i,
+			GLTFNodeIndex p_parent_node_index,
+			GLTFNodeIndex p_root_gltf_node,
+			Ref<GLTFNode> gltf_node);
+	void _convert_animation_player_to_gltf(
+			AnimationPlayer *animation_player, Ref<GLTFState> state,
+			const GLTFNodeIndex &p_gltf_current,
+			const GLTFNodeIndex &p_gltf_root_index,
+			Ref<GLTFNode> 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<GLTFState> state,
+			Spatial *spatial,
+			Ref<GLTFNode> 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<GLTFNode> gltf_node, Ref<GLTFState> 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<GLTFNode> gltf_node, Ref<GLTFState> state,
+			Node *p_root_node);
+	void _convert_skeleton_to_gltf(
+			Node *p_scene_parent, Ref<GLTFState> state,
+			const GLTFNodeIndex &p_parent_node_index,
+			const GLTFNodeIndex &p_root_node_index,
+			Ref<GLTFNode> gltf_node, Node *p_root_node);
+	void _convert_bone_attachment_to_gltf(Node *p_scene_parent,
+			Ref<GLTFState> state,
+			Ref<GLTFNode> gltf_node,
+			bool &retflag);
+	void _convert_mesh_to_gltf(Node *p_scene_parent,
+			Ref<GLTFState> state, Spatial *spatial,
+			Ref<GLTFNode> gltf_node);
+	void _convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
+			String p_animation_track_name);
+	Error serialize(Ref<GLTFState> state, Node *p_root, const String &p_path);
+	Error parse(Ref<GLTFState> state, String p_paths, bool p_read_binary = false);
+};
+
+#endif // GLTF_DOCUMENT_H

+ 101 - 0
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;
+}

+ 72 - 0
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

+ 57 - 0
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<float>
+}
+
+Ref<ArrayMesh> GLTFMesh::get_mesh() {
+	return mesh;
+}
+
+void GLTFMesh::set_mesh(Ref<ArrayMesh> p_mesh) {
+	mesh = p_mesh;
+}
+
+Vector<float> GLTFMesh::get_blend_weights() {
+	return blend_weights;
+}
+
+void GLTFMesh::set_blend_weights(Vector<float> p_blend_weights) {
+	blend_weights = p_blend_weights;
+}

+ 54 - 0
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<ArrayMesh> mesh;
+	Vector<float> blend_weights;
+
+protected:
+	static void _bind_methods();
+
+public:
+	Ref<ArrayMesh> get_mesh();
+	void set_mesh(Ref<ArrayMesh> p_mesh);
+	Vector<float> get_blend_weights();
+	void set_blend_weights(Vector<float> p_blend_weights);
+};
+#endif // GLTF_MESH_H

+ 178 - 0
modules/gltf/gltf_node.cpp

@@ -0,0 +1,178 @@
+/*************************************************************************/
+/*  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_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<int>
+	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<int> GLTFNode::get_children() {
+	return children;
+}
+
+void GLTFNode::set_children(Vector<int> p_children) {
+	children = p_children;
+}
+
+GLTFLightIndex GLTFNode::get_light() {
+	return light;
+}
+
+void GLTFNode::set_light(GLTFLightIndex p_light) {
+	light = p_light;
+}

+ 101 - 0
modules/gltf/gltf_node.h

@@ -0,0 +1,101 @@
+/*************************************************************************/
+/*  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<int> children;
+	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<int> get_children();
+	void set_children(Vector<int> p_children);
+
+	GLTFLightIndex get_light();
+	void set_light(GLTFLightIndex p_light);
+};
+#endif // GLTF_NODE_H

+ 95 - 0
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<GLTFNodeIndex>
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "roots"), "set_roots", "get_roots"); // PoolVector<GLTFNodeIndex>
+	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<String>
+	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<int32_t,
+}
+
+PoolVector<GLTFNodeIndex> GLTFSkeleton::get_joints() {
+	return joints;
+}
+
+void GLTFSkeleton::set_joints(PoolVector<GLTFNodeIndex> p_joints) {
+	joints = p_joints;
+}
+
+PoolVector<GLTFNodeIndex> GLTFSkeleton::get_roots() {
+	return roots;
+}
+
+void GLTFSkeleton::set_roots(PoolVector<GLTFNodeIndex> 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();
+}

+ 101 - 0
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<GLTFNodeIndex> joints;
+
+	// The roots of the skeleton. If there are multiple, each root must have the
+	// same parent (ie roots are siblings)
+	PoolVector<GLTFNodeIndex> roots;
+
+	// The created Skeleton for the scene
+	Skeleton *godot_skeleton = nullptr;
+
+	// Set of unique bone names for the skeleton
+	Set<String> unique_names;
+
+	Map<int32_t, GLTFNodeIndex> godot_bone_node;
+
+	PoolVector<BoneAttachment *> bone_attachments;
+
+protected:
+	static void _bind_methods();
+
+public:
+	PoolVector<GLTFNodeIndex> get_joints();
+	void set_joints(PoolVector<GLTFNodeIndex> p_joints);
+
+	PoolVector<GLTFNodeIndex> get_roots();
+	void set_roots(PoolVector<GLTFNodeIndex> 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<int32_t, GLTFNodeIndex> get_godot_bone_node() {
+	//	return this->godot_bone_node;
+	//}
+	//void set_godot_bone_node(Map<int32_t, GLTFNodeIndex> 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

+ 155 - 0
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<GLTFNodeIndex>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "inverse_binds", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "set_inverse_binds", "get_inverse_binds"); // Vector<Transform>
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "joints"), "set_joints", "get_joints"); // Vector<GLTFNodeIndex>
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "non_joints"), "set_non_joints", "get_non_joints"); // Vector<GLTFNodeIndex>
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "roots"), "set_roots", "get_roots"); // Vector<GLTFNodeIndex>
+	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<int,
+	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "joint_i_to_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "set_joint_i_to_name", "get_joint_i_to_name"); // Map<int,
+	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "godot_skin"), "set_godot_skin", "get_godot_skin"); // Ref<Skin>
+}
+
+GLTFNodeIndex GLTFSkin::get_skin_root() {
+	return skin_root;
+}
+
+void GLTFSkin::set_skin_root(GLTFNodeIndex p_skin_root) {
+	skin_root = p_skin_root;
+}
+
+Vector<GLTFNodeIndex> GLTFSkin::get_joints_original() {
+	return joints_original;
+}
+
+void GLTFSkin::set_joints_original(Vector<GLTFNodeIndex> 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<GLTFNodeIndex> GLTFSkin::get_joints() {
+	return joints;
+}
+
+void GLTFSkin::set_joints(Vector<GLTFNodeIndex> p_joints) {
+	joints = p_joints;
+}
+
+Vector<GLTFNodeIndex> GLTFSkin::get_non_joints() {
+	return non_joints;
+}
+
+void GLTFSkin::set_non_joints(Vector<GLTFNodeIndex> p_non_joints) {
+	non_joints = p_non_joints;
+}
+
+Vector<GLTFNodeIndex> GLTFSkin::get_roots() {
+	return roots;
+}
+
+void GLTFSkin::set_roots(Vector<GLTFNodeIndex> 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<int, StringName>::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<int, StringName>();
+	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<Skin> GLTFSkin::get_godot_skin() {
+	return godot_skin;
+}
+
+void GLTFSkin::set_godot_skin(Ref<Skin> p_godot_skin) {
+	godot_skin = p_godot_skin;
+}

+ 109 - 0
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<GLTFNodeIndex> joints_original;
+	Vector<Transform> 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<GLTFNodeIndex> joints;
+
+	// All Nodes that are caught in-between skin joint nodes, and are not
+	// defined as joints by any skin
+	Vector<GLTFNodeIndex> 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<GLTFNodeIndex> 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<int, int> joint_i_to_bone_i;
+	Map<int, StringName> 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<Skin> godot_skin;
+
+protected:
+	static void _bind_methods();
+
+public:
+	GLTFNodeIndex get_skin_root();
+	void set_skin_root(GLTFNodeIndex p_skin_root);
+
+	Vector<GLTFNodeIndex> get_joints_original();
+	void set_joints_original(Vector<GLTFNodeIndex> p_joints_original);
+
+	Array get_inverse_binds();
+	void set_inverse_binds(Array p_inverse_binds);
+
+	Vector<GLTFNodeIndex> get_joints();
+	void set_joints(Vector<GLTFNodeIndex> p_joints);
+
+	Vector<GLTFNodeIndex> get_non_joints();
+	void set_non_joints(Vector<GLTFNodeIndex> p_non_joints);
+
+	Vector<GLTFNodeIndex> get_roots();
+	void set_roots(Vector<GLTFNodeIndex> 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<Skin> get_godot_skin();
+	void set_godot_skin(Ref<Skin> p_godot_skin);
+};
+#endif // GLTF_SKIN_H

+ 90 - 0
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<Image>
+	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<Image>
+}
+
+Ref<Image> GLTFSpecGloss::get_diffuse_img() {
+	return diffuse_img;
+}
+
+void GLTFSpecGloss::set_diffuse_img(Ref<Image> 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<Image> GLTFSpecGloss::get_spec_gloss_img() {
+	return spec_gloss_img;
+}
+
+void GLTFSpecGloss::set_spec_gloss_img(Ref<Image> p_spec_gloss_img) {
+	spec_gloss_img = p_spec_gloss_img;
+}

+ 67 - 0
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<Image> 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<Image> spec_gloss_img = nullptr;
+
+protected:
+	static void _bind_methods();
+
+public:
+	Ref<Image> get_diffuse_img();
+	void set_diffuse_img(Ref<Image> 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<Image> get_spec_gloss_img();
+	void set_spec_gloss_img(Ref<Image> p_spec_gloss_img);
+};
+#endif // GLTF_SPEC_GLOSS_H

+ 307 - 0
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<uint8_t>
+	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<Ref<GLTFNode>>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "buffers"), "set_buffers", "get_buffers"); // Vector<Vector<uint8_t>
+	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<Ref<GLTFBufferView>>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "accessors", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_accessors", "get_accessors"); // Vector<Ref<GLTFAccessor>>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "meshes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_meshes", "get_meshes"); // Vector<Ref<GLTFMesh>>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "materials", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_materials", "get_materials"); // Vector<Ref<Material>
+	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<int>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_textures", "get_textures"); // Vector<Ref<GLTFTexture>>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "images", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_images", "get_images"); // Vector<Ref<Texture>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skins", "get_skins"); // Vector<Ref<GLTFSkin>>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "cameras", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_cameras", "get_cameras"); // Vector<Ref<GLTFCamera>>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lights", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_lights", "get_lights"); // Vector<Ref<GLTFLight>>
+	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<String>
+	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<String>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skeletons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeletons", "get_skeletons"); // Vector<Ref<GLTFSkeleton>>
+	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<GLTFSkeletonIndex,
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector<Ref<GLTFAnimation>>
+}
+
+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<uint8_t> GLTFState::get_glb_data() {
+	return glb_data;
+}
+
+void GLTFState::set_glb_data(Vector<uint8_t> 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];
+}

+ 186 - 0
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<uint8_t> glb_data;
+
+	bool use_named_skin_binds = false;
+	bool use_legacy_names = false;
+
+	Vector<Ref<GLTFNode>> nodes;
+	Vector<Vector<uint8_t>> buffers;
+	Vector<Ref<GLTFBufferView>> buffer_views;
+	Vector<Ref<GLTFAccessor>> accessors;
+
+	Vector<Ref<GLTFMesh>> meshes; // meshes are loaded directly, no reason not to.
+
+	Vector<AnimationPlayer *> animation_players;
+	Map<Ref<Material>, GLTFMaterialIndex> material_cache;
+	Vector<Ref<Material>> materials;
+
+	String scene_name;
+	Vector<int> root_nodes;
+	Vector<Ref<GLTFTexture>> textures;
+	Vector<Ref<Texture>> images;
+
+	Vector<Ref<GLTFSkin>> skins;
+	Vector<Ref<GLTFCamera>> cameras;
+	Vector<Ref<GLTFLight>> lights;
+	Set<String> unique_names;
+	Set<String> unique_animation_names;
+
+	Vector<Ref<GLTFSkeleton>> skeletons;
+	Map<GLTFSkeletonIndex, GLTFNodeIndex> skeleton_to_node;
+	Vector<Ref<GLTFAnimation>> animations;
+	Map<GLTFNodeIndex, Node *> 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<uint8_t> get_glb_data();
+	void set_glb_data(Vector<uint8_t> 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<GLTFNodeIndex, Node *> p_scene_nodes) {
+	//	this->scene_nodes = p_scene_nodes;
+	//}
+
+	//void set_animation_players(Vector<AnimationPlayer *> p_animation_players) {
+	//	this->animation_players = p_animation_players;
+	//}
+
+	//Map<Ref<Material>, GLTFMaterialIndex> get_material_cache() {
+	//	return this->material_cache;
+	//}
+	//void set_material_cache(Map<Ref<Material>, GLTFMaterialIndex> p_material_cache) {
+	//	this->material_cache = p_material_cache;
+	//}
+};
+#endif // GLTF_STATE_H

+ 46 - 0
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;
+}

+ 51 - 0
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

+ 88 - 0
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<EditorSceneImporterGLTF> 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<EditorSceneImporterGLTF>();
+	ClassDB::register_class<GLTFMesh>();
+	EditorPlugins::add_by_type<SceneExporterGLTFPlugin>();
+	ClassDB::set_current_api(prev_api);
+	EditorNode::add_init_callback(_editor_init);
+#endif
+	ClassDB::register_class<GLTFSpecGloss>();
+	ClassDB::register_class<GLTFNode>();
+	ClassDB::register_class<GLTFAnimation>();
+	ClassDB::register_class<GLTFBufferView>();
+	ClassDB::register_class<GLTFAccessor>();
+	ClassDB::register_class<GLTFTexture>();
+	ClassDB::register_class<GLTFSkeleton>();
+	ClassDB::register_class<GLTFSkin>();
+	ClassDB::register_class<GLTFCamera>();
+	ClassDB::register_class<GLTFLight>();
+	ClassDB::register_class<GLTFState>();
+	ClassDB::register_class<GLTFDocument>();
+	ClassDB::register_class<PackedSceneGLTF>();
+#endif
+}
+
+void unregister_gltf_types() {
+}

+ 32 - 0
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();