diff --git a/core/io/resource_exporter.cpp b/core/io/resource_exporter.cpp new file mode 100644 index 000000000000..3e813995abd7 --- /dev/null +++ b/core/io/resource_exporter.cpp @@ -0,0 +1,426 @@ +/*************************************************************************/ +/* resource_exporter.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 "resource_exporter.h" + +#include "core/os/os.h" +#include "core/variant_parser.h" + +bool ResourceFormatExporter::SortExporterByName::operator()(const Ref &p_a, const Ref &p_b) const { + return p_a->get_exporter_name() < p_b->get_exporter_name(); +} + +Error ResourceFormatExporter::_get_path_and_type(const String &p_path, PathAndType &r_path_and_type, bool *r_valid) const { + + Error err; + FileAccess *f = FileAccess::open(p_path + ".import", FileAccess::READ, &err); + + if (!f) { + if (r_valid) { + *r_valid = false; + } + return err; + } + + VariantParser::StreamFile stream; + stream.f = f; + + String assign; + Variant value; + VariantParser::Tag next_tag; + + if (r_valid) { + *r_valid = true; + } + + int lines = 0; + String error_text; + bool path_found = false; //first match must have priority + while (true) { + + assign = Variant(); + next_tag.fields.clear(); + next_tag.name = String(); + + err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, NULL, true); + if (err == ERR_FILE_EOF) { + memdelete(f); + return OK; + } else if (err != OK) { + ERR_PRINTS("ResourceFormatExporter::load - " + p_path + ".import:" + itos(lines) + " error: " + error_text); + memdelete(f); + return err; + } + + if (assign != String()) { + if (!path_found && assign.begins_with("path.") && r_path_and_type.path == String()) { + String feature = assign.get_slicec('.', 1); + if (OS::get_singleton()->has_feature(feature)) { + r_path_and_type.path = value; + path_found = true; //first match must have priority + } + + } else if (!path_found && assign == "path") { + r_path_and_type.path = value; + path_found = true; //first match must have priority + } else if (assign == "type") { + r_path_and_type.type = value; + } else if (assign == "exporter") { + r_path_and_type.exporter = value; + } else if (assign == "group_file") { + r_path_and_type.group_file = value; + } else if (assign == "metadata") { + r_path_and_type.metadata = value; + } else if (assign == "valid") { + if (r_valid) { + *r_valid = value; + } + } + + } else if (next_tag.name != "remap") { + break; + } + } + + memdelete(f); + + if (r_path_and_type.path == String() || r_path_and_type.type == String()) { + return ERR_FILE_CORRUPT; + } + return OK; +} +Error ResourceFormatExporter::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { + + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err != OK) { + return err; + } + + return ResourceSaver::save(pat.path, p_resource); +} + +void ResourceFormatExporter::get_recognized_exporter_extensions(List *p_extensions) const { + Set found; + + for (int i = 0; i < exporters.size(); i++) { + List local_exts; + exporters[i]->get_recognized_extensions(&local_exts); + for (List::Element *F = local_exts.front(); F; F = F->next()) { + if (!found.has(F->get())) { + p_extensions->push_back(F->get()); + found.insert(F->get()); + } + } + } +} + +void ResourceFormatExporter::get_recognized_exporter_extensions_for_type(const String &p_type, List *p_extensions) const { + + if (p_type == "") { + get_recognized_exporter_extensions(p_extensions); + return; + } + + Set found; + + for (int i = 0; i < exporters.size(); i++) { + String res_type = exporters[i]->get_resource_type(); + if (res_type == String()) + continue; + + if (!ClassDB::is_parent_class(res_type, p_type)) + continue; + + List local_exts; + exporters[i]->get_recognized_extensions(&local_exts); + for (List::Element *F = local_exts.front(); F; F = F->next()) { + if (!found.has(F->get())) { + p_extensions->push_back(F->get()); + found.insert(F->get()); + } + } + } +} + +bool ResourceFormatExporter::exists(const String &p_path) const { + + return FileAccess::exists(p_path); +} + +bool ResourceFormatExporter::recognize_path(const String &p_path) const { + + return FileAccess::exists(p_path); +} + +bool ResourceFormatExporter::can_be_exported(const String &p_path) const { + + return ResourceFormatSaver::recognize_path(p_path); +} + +int ResourceFormatExporter::get_export_order(const String &p_path) const { + + Ref exporter; + + if (FileAccess::exists(p_path + ".import")) { + + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err == OK) { + exporter = get_exporter_by_name(pat.exporter); + } + } else { + + exporter = get_exporter_by_extension(p_path.get_extension().to_lower()); + } + + if (exporter.is_valid()) + return exporter->get_export_order(); + + return 0; +} + +bool ResourceFormatExporter::handles_type(const String &p_type) const { + + for (int i = 0; i < exporters.size(); i++) { + + String res_type = exporters[i]->get_resource_type(); + if (res_type == String()) + continue; + if (ClassDB::is_parent_class(res_type, p_type)) + return true; + } + + return true; +} + +String ResourceFormatExporter::get_internal_resource_path(const String &p_path) const { + + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err != OK) { + + return String(); + } + + return pat.path; +} + +void ResourceFormatExporter::get_internal_resource_path_list(const String &p_path, List *r_paths) { + + Error err; + FileAccess *f = FileAccess::open(p_path + ".import", FileAccess::READ, &err); + + if (!f) + return; + + VariantParser::StreamFile stream; + stream.f = f; + + String assign; + Variant value; + VariantParser::Tag next_tag; + + int lines = 0; + String error_text; + while (true) { + + assign = Variant(); + next_tag.fields.clear(); + next_tag.name = String(); + + err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, NULL, true); + if (err == ERR_FILE_EOF) { + memdelete(f); + return; + } else if (err != OK) { + ERR_PRINTS("ResourceFormatExporter::get_internal_resource_path_list - " + p_path + ".import:" + itos(lines) + " error: " + error_text); + memdelete(f); + return; + } + + if (assign != String()) { + if (assign.begins_with("path.")) { + r_paths->push_back(value); + } else if (assign == "path") { + r_paths->push_back(value); + } + } else if (next_tag.name != "remap") { + break; + } + } + memdelete(f); +} + +String ResourceFormatExporter::get_export_group_file(const String &p_path) const { + + bool valid = true; + PathAndType pat; + _get_path_and_type(p_path, pat, &valid); + return valid ? pat.group_file : String(); +} + +bool ResourceFormatExporter::is_export_valid(const String &p_path) const { + + bool valid = true; + PathAndType pat; + _get_path_and_type(p_path, pat, &valid); + return valid; +} + +String ResourceFormatExporter::get_resource_type(const String &p_path) const { + + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err != OK) { + + return ""; + } + + return pat.type; +} + +Variant ResourceFormatExporter::get_resource_metadata(const String &p_path) const { + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err != OK) { + + return Variant(); + } + + return pat.metadata; +} + +void ResourceFormatExporter::get_dependencies(const String &p_path, List *p_dependencies, bool p_add_types) { + + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err != OK) { + + return; + } + + ResourceLoader::get_dependencies(pat.path, p_dependencies, p_add_types); +} + +Ref ResourceFormatExporter::get_exporter_by_name(const String &p_name) const { + + for (int i = 0; i < exporters.size(); i++) { + if (exporters[i]->get_exporter_name() == p_name) { + return exporters[i]; + } + } + + return Ref(); +} + +void ResourceFormatExporter::get_exporters_for_extension(const String &p_extension, List > *r_exporters) { + + for (int i = 0; i < exporters.size(); i++) { + List local_exts; + exporters[i]->get_recognized_extensions(&local_exts); + for (List::Element *F = local_exts.front(); F; F = F->next()) { + if (p_extension.to_lower() == F->get()) { + r_exporters->push_back(exporters[i]); + } + } + } +} + +Ref ResourceFormatExporter::get_exporter_by_extension(const String &p_extension) const { + + Ref exporter; + float priority = 0; + + for (int i = 0; i < exporters.size(); i++) { + + List local_exts; + exporters[i]->get_recognized_extensions(&local_exts); + for (List::Element *F = local_exts.front(); F; F = F->next()) { + if (p_extension.to_lower() == F->get() && exporters[i]->get_priority() > priority) { + exporter = exporters[i]; + priority = exporters[i]->get_priority(); + } + } + } + + return exporter; +} + +String ResourceFormatExporter::get_export_base_path(const String &p_for_file) const { + + return "res://.import/" + p_for_file.get_file() + "-" + p_for_file.md5_text(); +} + +bool ResourceFormatExporter::are_export_settings_valid(const String &p_path) const { + + bool valid = true; + PathAndType pat; + _get_path_and_type(p_path, pat, &valid); + + if (!valid) { + return false; + } + + for (int i = 0; i < exporters.size(); i++) { + if (exporters[i]->get_exporter_name() == pat.exporter) { + if (!exporters[i]->are_export_settings_valid(p_path)) { //importer thinks this is not valid + return false; + } + } + } + + return true; +} + +String ResourceFormatExporter::get_export_settings_hash() const { + + Vector > sorted_exporters = exporters; + + sorted_exporters.sort_custom(); + + String hash; + for (int i = 0; i < sorted_exporters.size(); i++) { + hash += ":" + sorted_exporters[i]->get_exporter_name() + ":" + sorted_exporters[i]->get_export_settings_string(); + } + return hash.md5_text(); +} + +ResourceFormatExporter *ResourceFormatExporter::singleton = NULL; + +ResourceFormatExporter::ResourceFormatExporter() { + singleton = this; +} diff --git a/core/io/resource_exporter.h b/core/io/resource_exporter.h new file mode 100644 index 000000000000..0405612c2f57 --- /dev/null +++ b/core/io/resource_exporter.h @@ -0,0 +1,138 @@ +/*************************************************************************/ +/* resource_exporter.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 RESOURCE_EXPORTER_H +#define RESOURCE_EXPORTER_H + +#include "core/io/resource_loader.h" +#include "resource_saver.h" + +class ResourceExporter; + +class ResourceFormatExporter : public ResourceFormatSaver { + + struct PathAndType { + String path; + String type; + String exporter; + String group_file; + Variant metadata; + }; + + Error _get_path_and_type(const String &p_path, PathAndType &r_path_and_type, bool *r_valid = NULL) const; + + static ResourceFormatExporter *singleton; + + //need them to stay in order to compute the settings hash + struct SortExporterByName { + bool operator()(const Ref &p_a, const Ref &p_b) const; + }; + + Vector > exporters; + +public: + static ResourceFormatExporter *get_singleton() { return singleton; } + virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); + virtual void get_recognized_exporter_extensions(List *p_extensions) const; + virtual void get_recognized_exporter_extensions_for_type(const String &p_type, List *p_extensions) const; + virtual bool recognize_path(const String &p_path) const; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; + virtual Variant get_resource_metadata(const String &p_path) const; + virtual bool is_export_valid(const String &p_path) const; + virtual void get_dependencies(const String &p_path, List *p_dependencies, bool p_add_types = false); + virtual bool is_exported(const String &p_path) const { return recognize_path(p_path); } + //TODO + virtual String get_export_group_file(const String &p_path) const; + virtual bool exists(const String &p_path) const; + //TODO + virtual bool can_be_exported(const String &p_path) const; + //TODO + virtual int get_export_order(const String &p_path) const; + + String get_internal_resource_path(const String &p_path) const; + void get_internal_resource_path_list(const String &p_path, List *r_paths); + + void add_exporter(const Ref &p_exporter) { + exporters.push_back(p_exporter); + } + void remove_exporter(const Ref &p_exporter) { exporters.erase(p_exporter); } + Ref get_exporter_by_name(const String &p_name) const; + Ref get_exporter_by_extension(const String &p_extension) const; + void get_exporters_for_extension(const String &p_extension, List > *r_exporters); + + bool are_export_settings_valid(const String &p_path) const; + //TODO + String get_export_settings_hash() const; + //TODO + String get_export_base_path(const String &p_for_file) const; + ResourceFormatExporter(); + ~ResourceFormatExporter() {} +}; + +class ResourceExporter : public Reference { + + GDCLASS(ResourceExporter, Reference); + +public: + virtual String get_exporter_name() const = 0; + virtual String get_visible_name() const = 0; + virtual void get_recognized_extensions(List *p_extensions) const = 0; + virtual String get_save_extension() const = 0; + virtual String get_resource_type() const = 0; + virtual float get_priority() const { return 1.0; } + virtual int get_export_order() const { return 0; } + + struct ExportOption { + PropertyInfo option; + Variant default_value; + + ExportOption(const PropertyInfo &p_info, const Variant &p_default) : + option(p_info), + default_value(p_default) { + } + ExportOption() {} + }; + + virtual int get_preset_count() const { return 0; } + virtual String get_preset_name(int p_idx) const { return String(); } + + virtual void get_export_options(List *r_options, int p_preset = 0) const = 0; + virtual bool get_option_visibility(const String &p_option, const Map &p_options) const = 0; + virtual String get_option_group_file() const { return String(); } + + virtual Error export_(Node *p_node, const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files = NULL, Variant *r_metadata = NULL) = 0; + + virtual Error export_group_file(const String &p_group_file, const Map > &p_source_file_options, const Map &p_base_paths) { return ERR_UNAVAILABLE; } + virtual bool are_export_settings_valid(const String &p_path) const { return true; } + virtual String get_export_settings_string() const { return String(); } +}; + +#endif // RESOURCE_EXPORTER_H diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp index 7aa8732366e0..3a88a2b2fb8f 100644 --- a/core/io/resource_saver.cpp +++ b/core/io/resource_saver.cpp @@ -40,6 +40,10 @@ int ResourceSaver::saver_count = 0; bool ResourceSaver::timestamp_on_save = false; ResourceSavedCallback ResourceSaver::save_callback = 0; +bool ResourceFormatSaver::recognize_path(const String &p_path) const { + return FileAccess::exists(p_path); +} + Error ResourceFormatSaver::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { if (get_script_instance() && get_script_instance()->has_method("save")) { diff --git a/core/io/resource_saver.h b/core/io/resource_saver.h index 20e05d827aa0..a4cf2dc51aa3 100644 --- a/core/io/resource_saver.h +++ b/core/io/resource_saver.h @@ -31,6 +31,8 @@ #ifndef RESOURCE_SAVER_H #define RESOURCE_SAVER_H +#include "core/io/resource_saver.h" +#include "core/os/file_access.h" #include "core/resource.h" class ResourceFormatSaver : public Reference { @@ -42,6 +44,7 @@ class ResourceFormatSaver : public Reference { public: virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); virtual bool recognize(const RES &p_resource) const; + virtual bool recognize_path(const String &p_path) const; virtual void get_recognized_extensions(const RES &p_resource, List *p_extensions) const; virtual ~ResourceFormatSaver() {} @@ -49,8 +52,8 @@ class ResourceFormatSaver : public Reference { typedef void (*ResourceSavedCallback)(Ref p_resource, const String &p_path); -class ResourceSaver { - +class ResourceSaver : public Reference { + GDCLASS(ResourceSaver, Reference); enum { MAX_SAVERS = 64 }; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index efc77bde4899..4635763c62ad 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -48,6 +48,7 @@ #include "core/io/packet_peer.h" #include "core/io/packet_peer_udp.h" #include "core/io/pck_packer.h" +#include "core/io/resource_exporter.h" #include "core/io/resource_format_binary.h" #include "core/io/resource_importer.h" #include "core/io/stream_peer_ssl.h" @@ -70,6 +71,7 @@ static Ref resource_saver_binary; static Ref resource_loader_binary; static Ref resource_format_importer; +static Ref resource_format_exporter; static Ref resource_format_image; static Ref resource_format_po; static Ref resource_format_saver_crypto; @@ -121,6 +123,9 @@ void register_core_types() { resource_format_importer.instance(); ResourceLoader::add_resource_format_loader(resource_format_importer); + resource_format_exporter.instance(); + ResourceSaver::add_resource_format_saver(resource_format_exporter); + resource_format_image.instance(); ResourceLoader::add_resource_format_loader(resource_format_image); @@ -288,6 +293,9 @@ void unregister_core_types() { ResourceLoader::remove_resource_format_loader(resource_format_importer); resource_format_importer.unref(); + ResourceSaver::remove_resource_format_saver(resource_format_exporter); + resource_format_exporter.unref(); + ResourceLoader::remove_resource_format_loader(resource_format_po); resource_format_po.unref(); diff --git a/editor/SCsub b/editor/SCsub index 2b560f68e816..d82b2a305ec7 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -85,6 +85,7 @@ if env['tools']: SConscript('fileserver/SCsub') SConscript('icons/SCsub') SConscript('import/SCsub') + SConscript('saver/SCsub') SConscript('plugins/SCsub') lib = env.add_library("editor", env.editor_sources) diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index ab41019ac31f..6399fc56b1ea 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -52,6 +52,7 @@ #include "scene/resources/packed_scene.h" #include "servers/physics_2d_server.h" +#include "core/io/resource_exporter.h" #include "editor/editor_audio_buses.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" @@ -60,7 +61,6 @@ #include "editor/editor_spin_slider.h" #include "editor/editor_themes.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.h" #include "editor/import/resource_importer_csv_translation.h" @@ -130,6 +130,7 @@ #include "editor/plugins/visual_shader_editor_plugin.h" #include "editor/pvrtc_compress.h" #include "editor/register_exporters.h" +#include "editor/saver/resource_saver_scene.h" #include "editor/script_editor_debugger.h" #include @@ -5636,15 +5637,15 @@ EditorNode::EditorNode() { import_obj2.instance(); import_scene->add_importer(import_obj2); - Ref import_gltf; - import_gltf.instance(); - import_scene->add_importer(import_gltf); - Ref import_escn; import_escn.instance(); import_scene->add_importer(import_escn); } + Ref export_scene; + export_scene.instance(); + ResourceFormatExporter::get_singleton()->add_exporter(export_scene); + Ref import_bitmap; import_bitmap.instance(); ResourceFormatImporter::get_singleton()->add_importer(import_bitmap); diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp deleted file mode 100644 index be066e15a555..000000000000 --- a/editor/import/editor_scene_importer_gltf.cpp +++ /dev/null @@ -1,3067 +0,0 @@ -/*************************************************************************/ -/* editor_scene_importer_gltf.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 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/resources/surface_tool.h" - -uint32_t EditorSceneImporterGLTF::get_import_flags() const { - - return IMPORT_SCENE | IMPORT_ANIMATION; -} -void EditorSceneImporterGLTF::get_extensions(List *r_extensions) const { - - r_extensions->push_back("gltf"); - r_extensions->push_back("glb"); -} - -Error EditorSceneImporterGLTF::_parse_json(const String &p_path, GLTFState &state) { - - Error err; - FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); - if (!f) { - return err; - } - - Vector array; - array.resize(f->get_len()); - f->get_buffer(array.ptrw(), array.size()); - String text; - text.parse_utf8((const char *)array.ptr(), array.size()); - - String err_txt; - int err_line; - Variant v; - err = JSON::parse(text, v, err_txt, err_line); - if (err != OK) { - _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); - return err; - } - state.json = v; - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_glb(const String &p_path, GLTFState &state) { - - Error err; - FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); - if (!f) { - return err; - } - - uint32_t magic = f->get_32(); - ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF - f->get_32(); // version - f->get_32(); // length - - uint32_t chunk_length = f->get_32(); - uint32_t chunk_type = f->get_32(); - - ERR_FAIL_COND_V(chunk_type != 0x4E4F534A, ERR_PARSE_ERROR); //JSON - Vector json_data; - json_data.resize(chunk_length); - uint32_t len = f->get_buffer(json_data.ptrw(), chunk_length); - ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); - - String text; - text.parse_utf8((const char *)json_data.ptr(), json_data.size()); - - String err_txt; - int err_line; - Variant v; - err = JSON::parse(text, v, err_txt, err_line); - if (err != OK) { - _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); - return err; - } - - state.json = v; - - //data? - - chunk_length = f->get_32(); - chunk_type = f->get_32(); - - if (f->eof_reached()) { - return OK; //all good - } - - ERR_FAIL_COND_V(chunk_type != 0x004E4942, ERR_PARSE_ERROR); //BIN - - state.glb_data.resize(chunk_length); - len = f->get_buffer(state.glb_data.ptrw(), chunk_length); - ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); - - return OK; -} - -static Vector3 _arr_to_vec3(const Array &p_array) { - ERR_FAIL_COND_V(p_array.size() != 3, Vector3()); - return Vector3(p_array[0], p_array[1], p_array[2]); -} - -static Quat _arr_to_quat(const Array &p_array) { - ERR_FAIL_COND_V(p_array.size() != 4, Quat()); - return Quat(p_array[0], p_array[1], p_array[2], p_array[3]); -} - -static Transform _arr_to_xform(const Array &p_array) { - ERR_FAIL_COND_V(p_array.size() != 16, Transform()); - - Transform xform; - xform.basis.set_axis(Vector3::AXIS_X, Vector3(p_array[0], p_array[1], p_array[2])); - xform.basis.set_axis(Vector3::AXIS_Y, Vector3(p_array[4], p_array[5], p_array[6])); - xform.basis.set_axis(Vector3::AXIS_Z, Vector3(p_array[8], p_array[9], p_array[10])); - xform.set_origin(Vector3(p_array[12], p_array[13], p_array[14])); - - return xform; -} - -String EditorSceneImporterGLTF::_sanitize_scene_name(const String &name) { - RegEx regex("([^a-zA-Z0-9_ -]+)"); - String p_name = regex.sub(name, "", true); - return p_name; -} - -String EditorSceneImporterGLTF::_gen_unique_name(GLTFState &state, const String &p_name) { - - const String s_name = _sanitize_scene_name(p_name); - - String name; - int index = 1; - while (true) { - name = s_name; - - if (index > 1) { - name += " " + itos(index); - } - if (!state.unique_names.has(name)) { - break; - } - index++; - } - - state.unique_names.insert(name); - - return name; -} - -String EditorSceneImporterGLTF::_sanitize_bone_name(const String &name) { - String p_name = name.camelcase_to_underscore(true); - - RegEx pattern_del("([^a-zA-Z0-9_ ])+"); - p_name = pattern_del.sub(p_name, "", true); - - RegEx pattern_nospace(" +"); - p_name = pattern_nospace.sub(p_name, "_", true); - - RegEx pattern_multiple("_+"); - p_name = pattern_multiple.sub(p_name, "_", true); - - RegEx pattern_padded("0+(\\d+)"); - p_name = pattern_padded.sub(p_name, "$1", true); - - return p_name; -} - -String EditorSceneImporterGLTF::_gen_unique_bone_name(GLTFState &state, const GLTFSkeletonIndex skel_i, const String &p_name) { - - const String s_name = _sanitize_bone_name(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("children")) { - const Array &children = n["children"]; - for (int j = 0; j < children.size(); j++) { - node->children.push_back(children[j]); - } - } - - state.nodes.push_back(node); - } - - // build the hierarchy - for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); node_i++) { - - for (int j = 0; j < state.nodes[node_i]->children.size(); j++) { - GLTFNodeIndex child_i = state.nodes[node_i]->children[j]; - - ERR_FAIL_INDEX_V(child_i, state.nodes.size(), ERR_FILE_CORRUPT); - ERR_CONTINUE(state.nodes[child_i]->parent != -1); //node already has a parent, wtf. - - state.nodes[child_i]->parent = node_i; - } - } - - _compute_node_heights(state); - - return OK; -} - -void EditorSceneImporterGLTF::_compute_node_heights(GLTFState &state) { - - state.root_nodes.clear(); - for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { - GLTFNode *node = state.nodes[node_i]; - node->height = 0; - - GLTFNodeIndex current_i = node_i; - while (current_i >= 0) { - const GLTFNodeIndex parent_i = state.nodes[current_i]->parent; - if (parent_i >= 0) { - ++node->height; - } - current_i = parent_i; - } - - if (node->height == 0) { - state.root_nodes.push_back(node_i); - } - } -} - -static Vector _parse_base64_uri(const String &uri) { - - int start = uri.find(","); - ERR_FAIL_COND_V(start == -1, Vector()); - - CharString substr = uri.right(start + 1).ascii(); - - int strlen = substr.length(); - - Vector buf; - buf.resize(strlen / 4 * 3 + 1 + 1); - - size_t len = 0; - ERR_FAIL_COND_V(CryptoCore::b64_decode(buf.ptrw(), buf.size(), &len, (unsigned char *)substr.get_data(), strlen) != OK, Vector()); - - buf.resize(len); - - return buf; -} - -Error EditorSceneImporterGLTF::_parse_buffers(GLTFState &state, const String &p_base_path) { - - if (!state.json.has("buffers")) - return OK; - - const Array &buffers = state.json["buffers"]; - for (GLTFBufferIndex i = 0; i < buffers.size(); i++) { - - if (i == 0 && state.glb_data.size()) { - state.buffers.push_back(state.glb_data); - - } else { - const Dictionary &buffer = buffers[i]; - if (buffer.has("uri")) { - - Vector buffer_data; - String uri = buffer["uri"]; - - if (uri.findn("data:application/octet-stream;base64") == 0) { - //embedded data - buffer_data = _parse_base64_uri(uri); - } else { - - uri = p_base_path.plus_file(uri).replace("\\", "/"); //fix for windows - buffer_data = FileAccess::get_file_as_array(uri); - ERR_FAIL_COND_V(buffer.size() == 0, ERR_PARSE_ERROR); - } - - 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) { - - ERR_FAIL_COND_V(!state.json.has("bufferViews"), ERR_FILE_CORRUPT); - 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) { - - ERR_FAIL_COND_V(!state.json.has("accessors"), ERR_FILE_CORRUPT); - 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("max")) { - accessor.max = d["max"]; - } - - if (d.has("min")) { - accessor.min = d["min"]; - } - - if (d.has("sparse")) { - //eeh.. - - const Dictionary &s = d["sparse"]; - - ERR_FAIL_COND_V(!s.has("count"), ERR_PARSE_ERROR); - accessor.sparse_count = s["count"]; - ERR_FAIL_COND_V(!s.has("indices"), ERR_PARSE_ERROR); - const Dictionary &si = s["indices"]; - - ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR); - accessor.sparse_indices_buffer_view = si["bufferView"]; - ERR_FAIL_COND_V(!si.has("componentType"), ERR_PARSE_ERROR); - accessor.sparse_indices_component_type = si["componentType"]; - - if (si.has("byteOffset")) { - accessor.sparse_indices_byte_offset = si["byteOffset"]; - } - - ERR_FAIL_COND_V(!s.has("values"), ERR_PARSE_ERROR); - const Dictionary &sv = s["values"]; - - ERR_FAIL_COND_V(!sv.has("bufferView"), ERR_PARSE_ERROR); - accessor.sparse_values_buffer_view = sv["bufferView"]; - if (sv.has("byteOffset")) { - accessor.sparse_values_byte_offset = sv["byteOffset"]; - } - } - - state.accessors.push_back(accessor); - } - - print_verbose("glTF: Total accessors: " + itos(state.accessors.size())); - - return OK; -} - -String EditorSceneImporterGLTF::_get_component_type_name(const uint32_t p_component) { - - switch (p_component) { - case COMPONENT_TYPE_BYTE: return "Byte"; - case COMPONENT_TYPE_UNSIGNED_BYTE: return "UByte"; - case COMPONENT_TYPE_SHORT: return "Short"; - case COMPONENT_TYPE_UNSIGNED_SHORT: return "UShort"; - case COMPONENT_TYPE_INT: return "Int"; - case COMPONENT_TYPE_FLOAT: return "Float"; - } - - return ""; -} - -String EditorSceneImporterGLTF::_get_type_name(const GLTFType p_component) { - - static const char *names[] = { - "float", - "vec2", - "vec3", - "vec4", - "mat2", - "mat3", - "mat4" - }; - - return names[p_component]; -} - -Error EditorSceneImporterGLTF::_decode_buffer_view(GLTFState &state, double *dst, const GLTFBufferViewIndex p_buffer_view, const int skip_every, const int skip_bytes, const int element_size, const int count, const GLTFType type, const int component_count, const int component_type, const int component_size, const bool normalized, const int byte_offset, const bool for_vertex) { - - const GLTFBufferView &bv = state.buffer_views[p_buffer_view]; - - int stride = bv.byte_stride ? bv.byte_stride : element_size; - if (for_vertex && stride % 4) { - stride += 4 - (stride % 4); //according to spec must be multiple of 4 - } - - ERR_FAIL_INDEX_V(bv.buffer, state.buffers.size(), ERR_PARSE_ERROR); - - const uint32_t offset = bv.byte_offset + byte_offset; - Vector buffer = state.buffers[bv.buffer]; //copy on write, so no performance hit - const uint8_t *bufptr = buffer.ptr(); - - //use to debug - print_verbose("glTF: type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); - print_verbose("glTF: accessor offset" + itos(byte_offset) + " view offset: " + itos(bv.byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv.byte_length)); - - const int buffer_end = (stride * (count - 1)) + element_size; - ERR_FAIL_COND_V(buffer_end > bv.byte_length, ERR_PARSE_ERROR); - - ERR_FAIL_COND_V((int)(offset + buffer_end) > buffer.size(), ERR_PARSE_ERROR); - - //fill everything as doubles - - for (int i = 0; i < count; i++) { - - const uint8_t *src = &bufptr[offset + i * stride]; - - for (int j = 0; j < component_count; j++) { - - if (skip_every && j > 0 && (j % skip_every) == 0) { - src += skip_bytes; - } - - double d = 0; - - switch (component_type) { - case COMPONENT_TYPE_BYTE: { - int8_t b = int8_t(*src); - if (normalized) { - d = (double(b) / 128.0); - } else { - d = double(b); - } - } break; - case COMPONENT_TYPE_UNSIGNED_BYTE: { - uint8_t b = *src; - if (normalized) { - d = (double(b) / 255.0); - } else { - d = double(b); - } - } break; - case COMPONENT_TYPE_SHORT: { - int16_t s = *(int16_t *)src; - if (normalized) { - d = (double(s) / 32768.0); - } else { - d = double(s); - } - } break; - case COMPONENT_TYPE_UNSIGNED_SHORT: { - uint16_t s = *(uint16_t *)src; - if (normalized) { - d = (double(s) / 65535.0); - } else { - d = double(s); - } - - } break; - case COMPONENT_TYPE_INT: { - d = *(int *)src; - } break; - case COMPONENT_TYPE_FLOAT: { - d = *(float *)src; - } break; - } - - *dst++ = d; - src += component_size; - } - } - - return OK; -} - -int EditorSceneImporterGLTF::_get_component_type_size(const int component_type) { - - switch (component_type) { - case COMPONENT_TYPE_BYTE: return 1; break; - case COMPONENT_TYPE_UNSIGNED_BYTE: return 1; break; - case COMPONENT_TYPE_SHORT: return 2; break; - case COMPONENT_TYPE_UNSIGNED_SHORT: return 2; break; - case COMPONENT_TYPE_INT: return 4; break; - case COMPONENT_TYPE_FLOAT: return 4; break; - default: { - ERR_FAIL_V(0); - } - } - return 0; -} - -Vector EditorSceneImporterGLTF::_decode_accessor(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - //spec, for reference: - //https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - - ERR_FAIL_INDEX_V(p_accessor, state.accessors.size(), Vector()); - - const GLTFAccessor &a = state.accessors[p_accessor]; - - const int component_count_for_type[7] = { - 1, 2, 3, 4, 4, 9, 16 - }; - - const int component_count = component_count_for_type[a.type]; - const int component_size = _get_component_type_size(a.component_type); - ERR_FAIL_COND_V(component_size == 0, Vector()); - int element_size = component_count * component_size; - - int skip_every = 0; - int skip_bytes = 0; - //special case of alignments, as described in spec - switch (a.component_type) { - case COMPONENT_TYPE_BYTE: - case COMPONENT_TYPE_UNSIGNED_BYTE: { - - if (a.type == TYPE_MAT2) { - skip_every = 2; - skip_bytes = 2; - element_size = 8; //override for this case - } - if (a.type == TYPE_MAT3) { - skip_every = 3; - skip_bytes = 1; - element_size = 12; //override for this case - } - - } break; - case COMPONENT_TYPE_SHORT: - case COMPONENT_TYPE_UNSIGNED_SHORT: { - if (a.type == TYPE_MAT3) { - skip_every = 6; - skip_bytes = 4; - element_size = 16; //override for this case - } - } break; - default: { - } - } - - Vector dst_buffer; - dst_buffer.resize(component_count * a.count); - double *dst = dst_buffer.ptrw(); - - if (a.buffer_view >= 0) { - - ERR_FAIL_INDEX_V(a.buffer_view, state.buffer_views.size(), Vector()); - - const Error err = _decode_buffer_view(state, dst, a.buffer_view, skip_every, skip_bytes, element_size, a.count, a.type, component_count, a.component_type, component_size, a.normalized, a.byte_offset, p_for_vertex); - if (err != OK) - return Vector(); - - } else { - //fill with zeros, as bufferview is not defined. - for (int i = 0; i < (a.count * component_count); i++) { - dst_buffer.write[i] = 0; - } - } - - if (a.sparse_count > 0) { - // I could not find any file using this, so this code is so far untested - Vector indices; - indices.resize(a.sparse_count); - const int indices_component_size = _get_component_type_size(a.sparse_indices_component_type); - - Error err = _decode_buffer_view(state, indices.ptrw(), a.sparse_indices_buffer_view, 0, 0, indices_component_size, a.sparse_count, TYPE_SCALAR, 1, a.sparse_indices_component_type, indices_component_size, false, a.sparse_indices_byte_offset, false); - if (err != OK) - return Vector(); - - Vector data; - data.resize(component_count * a.sparse_count); - err = _decode_buffer_view(state, data.ptrw(), a.sparse_values_buffer_view, skip_every, skip_bytes, element_size, a.sparse_count, a.type, component_count, a.component_type, component_size, a.normalized, a.sparse_values_byte_offset, p_for_vertex); - if (err != OK) - return Vector(); - - for (int i = 0; i < indices.size(); i++) { - const int write_offset = int(indices[i]) * component_count; - - for (int j = 0; j < component_count; j++) { - dst[write_offset + j] = data[i * component_count + j]; - } - } - } - - return dst_buffer; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_ints(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) - return ret; - - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size(); - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = int(attribs_ptr[i]); - } - } - return ret; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_floats(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) - return ret; - - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size(); - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = float(attribs_ptr[i]); - } - } - return ret; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_vec2(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) - return ret; - - ERR_FAIL_COND_V(attribs.size() % 2 != 0, ret); - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size() / 2; - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = Vector2(attribs_ptr[i * 2 + 0], attribs_ptr[i * 2 + 1]); - } - } - return ret; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_vec3(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) - return ret; - - ERR_FAIL_COND_V(attribs.size() % 3 != 0, ret); - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size() / 3; - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = Vector3(attribs_ptr[i * 3 + 0], attribs_ptr[i * 3 + 1], attribs_ptr[i * 3 + 2]); - } - } - return ret; -} - -PoolVector EditorSceneImporterGLTF::_decode_accessor_as_color(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - PoolVector ret; - - if (attribs.size() == 0) - return ret; - - const int type = state.accessors[p_accessor].type; - ERR_FAIL_COND_V(!(type == TYPE_VEC3 || type == TYPE_VEC4), ret); - int components; - if (type == TYPE_VEC3) { - components = 3; - } else { // TYPE_VEC4 - components = 4; - } - - ERR_FAIL_COND_V(attribs.size() % components != 0, ret); - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size() / components; - ret.resize(ret_size); - { - PoolVector::Write w = ret.write(); - for (int i = 0; i < ret_size; i++) { - w[i] = Color(attribs_ptr[i * 4 + 0], attribs_ptr[i * 4 + 1], attribs_ptr[i * 4 + 2], components == 4 ? attribs_ptr[i * 4 + 3] : 1.0); - } - } - return ret; -} -Vector EditorSceneImporterGLTF::_decode_accessor_as_quat(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - Vector ret; - - if (attribs.size() == 0) - return ret; - - ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); - const double *attribs_ptr = attribs.ptr(); - const int ret_size = attribs.size() / 4; - ret.resize(ret_size); - { - for (int i = 0; i < ret_size; i++) { - ret.write[i] = Quat(attribs_ptr[i * 4 + 0], attribs_ptr[i * 4 + 1], attribs_ptr[i * 4 + 2], attribs_ptr[i * 4 + 3]).normalized(); - } - } - return ret; -} -Vector EditorSceneImporterGLTF::_decode_accessor_as_xform2d(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - Vector ret; - - if (attribs.size() == 0) - return ret; - - ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); - ret.resize(attribs.size() / 4); - for (int i = 0; i < ret.size(); i++) { - ret.write[i][0] = Vector2(attribs[i * 4 + 0], attribs[i * 4 + 1]); - ret.write[i][1] = Vector2(attribs[i * 4 + 2], attribs[i * 4 + 3]); - } - return ret; -} - -Vector EditorSceneImporterGLTF::_decode_accessor_as_basis(GLTFState &state, const GLTFAccessorIndex p_accessor, bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - Vector ret; - - if (attribs.size() == 0) - return ret; - - ERR_FAIL_COND_V(attribs.size() % 9 != 0, ret); - ret.resize(attribs.size() / 9); - for (int i = 0; i < ret.size(); i++) { - ret.write[i].set_axis(0, Vector3(attribs[i * 9 + 0], attribs[i * 9 + 1], attribs[i * 9 + 2])); - ret.write[i].set_axis(1, Vector3(attribs[i * 9 + 3], attribs[i * 9 + 4], attribs[i * 9 + 5])); - ret.write[i].set_axis(2, Vector3(attribs[i * 9 + 6], attribs[i * 9 + 7], attribs[i * 9 + 8])); - } - return ret; -} - -Vector EditorSceneImporterGLTF::_decode_accessor_as_xform(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - - const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); - Vector ret; - - if (attribs.size() == 0) - return ret; - - ERR_FAIL_COND_V(attribs.size() % 16 != 0, ret); - ret.resize(attribs.size() / 16); - for (int i = 0; i < ret.size(); i++) { - ret.write[i].basis.set_axis(0, Vector3(attribs[i * 16 + 0], attribs[i * 16 + 1], attribs[i * 16 + 2])); - ret.write[i].basis.set_axis(1, Vector3(attribs[i * 16 + 4], attribs[i * 16 + 5], attribs[i * 16 + 6])); - ret.write[i].basis.set_axis(2, Vector3(attribs[i * 16 + 8], attribs[i * 16 + 9], attribs[i * 16 + 10])); - ret.write[i].set_origin(Vector3(attribs[i * 16 + 12], attribs[i * 16 + 13], attribs[i * 16 + 14])); - } - return ret; -} - -Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { - - if (!state.json.has("meshes")) - return OK; - - Array meshes = state.json["meshes"]; - for (GLTFMeshIndex i = 0; i < meshes.size(); i++) { - - print_verbose("glTF: Parsing mesh: " + itos(i)); - Dictionary d = meshes[i]; - - GLTFMesh mesh; - mesh.mesh.instance(); - - ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); - - Array primitives = d["primitives"]; - const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); - - for (int j = 0; j < primitives.size(); j++) { - - Dictionary p = primitives[j]; - - Array array; - array.resize(Mesh::ARRAY_MAX); - - ERR_FAIL_COND_V(!p.has("attributes"), ERR_PARSE_ERROR); - - Dictionary a = p["attributes"]; - - Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; - if (p.has("mode")) { - const int mode = p["mode"]; - ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT); - static const Mesh::PrimitiveType primitives2[7] = { - Mesh::PRIMITIVE_POINTS, - Mesh::PRIMITIVE_LINES, - Mesh::PRIMITIVE_LINE_LOOP, - Mesh::PRIMITIVE_LINE_STRIP, - Mesh::PRIMITIVE_TRIANGLES, - Mesh::PRIMITIVE_TRIANGLE_STRIP, - Mesh::PRIMITIVE_TRIANGLE_FAN, - }; - - primitive = primitives2[mode]; - } - - ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR); - if (a.has("POSITION")) { - array[Mesh::ARRAY_VERTEX] = _decode_accessor_as_vec3(state, a["POSITION"], true); - } - if (a.has("NORMAL")) { - array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); - } - if (a.has("TANGENT")) { - array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(state, a["TANGENT"], true); - } - if (a.has("TEXCOORD_0")) { - array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(state, a["TEXCOORD_0"], true); - } - if (a.has("TEXCOORD_1")) { - array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(state, a["TEXCOORD_1"], true); - } - if (a.has("COLOR_0")) { - array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(state, a["COLOR_0"], true); - } - if (a.has("JOINTS_0")) { - array[Mesh::ARRAY_BONES] = _decode_accessor_as_ints(state, a["JOINTS_0"], true); - } - if (a.has("WEIGHTS_0")) { - PoolVector weights = _decode_accessor_as_floats(state, a["WEIGHTS_0"], true); - { //gltf does not seem to normalize the weights for some reason.. - int wc = weights.size(); - PoolVector::Write w = weights.write(); - - for (int k = 0; k < wc; k += 4) { - float total = 0.0; - total += w[k + 0]; - total += w[k + 1]; - total += w[k + 2]; - total += w[k + 3]; - if (total > 0.0) { - w[k + 0] /= total; - w[k + 1] /= total; - w[k + 2] /= total; - w[k + 3] /= total; - } - } - } - array[Mesh::ARRAY_WEIGHTS] = weights; - } - - if (p.has("indices")) { - PoolVector indices = _decode_accessor_as_ints(state, p["indices"], false); - - if (primitive == Mesh::PRIMITIVE_TRIANGLES) { - //swap around indices, convert ccw to cw for front face - - const int is = indices.size(); - const PoolVector::Write w = indices.write(); - for (int k = 0; k < is; k += 3) { - SWAP(w[k + 1], w[k + 2]); - } - } - array[Mesh::ARRAY_INDEX] = indices; - - } else if (primitive == Mesh::PRIMITIVE_TRIANGLES) { - //generate indices because they need to be swapped for CW/CCW - const PoolVector &vertices = array[Mesh::ARRAY_VERTEX]; - ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR); - PoolVector indices; - const int vs = vertices.size(); - indices.resize(vs); - { - const PoolVector::Write w = indices.write(); - for (int k = 0; k < vs; k += 3) { - w[k] = k; - w[k + 1] = k + 2; - w[k + 2] = k + 1; - } - } - array[Mesh::ARRAY_INDEX] = indices; - } - - bool generated_tangents = false; - Variant erased_indices; - - if (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("TEXCOORD_0") && a.has("NORMAL")) { - //must generate mikktspace tangents.. ergh.. - Ref st; - st.instance(); - st->create_from_triangle_arrays(array); - if (!p.has("targets")) { - //morph targets should not be reindexed, as array size might differ - //removing indices is the best bet here - st->deindex(); - erased_indices = a[Mesh::ARRAY_INDEX]; - a[Mesh::ARRAY_INDEX] = Variant(); - } - st->generate_tangents(); - array = st->commit_to_arrays(); - generated_tangents = true; - } - - Array morphs; - //blend shapes - if (p.has("targets")) { - print_verbose("glTF: Mesh has targets"); - const Array &targets = p["targets"]; - - //ideally BLEND_SHAPE_MODE_RELATIVE since gltf2 stores in displacement - //but it could require a larger refactor? - mesh.mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); - - if (j == 0) { - const Array &target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array(); - for (int k = 0; k < targets.size(); k++) { - const String name = k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k); - mesh.mesh->add_blend_shape(name); - } - } - - for (int k = 0; k < targets.size(); k++) { - - const Dictionary &t = targets[k]; - - Array array_copy; - array_copy.resize(Mesh::ARRAY_MAX); - - for (int l = 0; l < Mesh::ARRAY_MAX; l++) { - array_copy[l] = array[l]; - } - - array_copy[Mesh::ARRAY_INDEX] = Variant(); - - if (t.has("POSITION")) { - PoolVector varr = _decode_accessor_as_vec3(state, t["POSITION"], true); - const PoolVector src_varr = array[Mesh::ARRAY_VERTEX]; - const int size = src_varr.size(); - ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); - { - - const int max_idx = varr.size(); - varr.resize(size); - - const PoolVector::Write w_varr = varr.write(); - const PoolVector::Read r_varr = varr.read(); - const PoolVector::Read r_src_varr = src_varr.read(); - for (int l = 0; l < size; l++) { - if (l < max_idx) { - w_varr[l] = r_varr[l] + r_src_varr[l]; - } else { - w_varr[l] = r_src_varr[l]; - } - } - } - array_copy[Mesh::ARRAY_VERTEX] = varr; - } - if (t.has("NORMAL")) { - PoolVector narr = _decode_accessor_as_vec3(state, t["NORMAL"], true); - const PoolVector src_narr = array[Mesh::ARRAY_NORMAL]; - int size = src_narr.size(); - ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); - { - int max_idx = narr.size(); - narr.resize(size); - - const PoolVector::Write w_narr = narr.write(); - const PoolVector::Read r_narr = narr.read(); - const PoolVector::Read r_src_narr = src_narr.read(); - for (int l = 0; l < size; l++) { - if (l < max_idx) { - w_narr[l] = r_narr[l] + r_src_narr[l]; - } else { - w_narr[l] = r_src_narr[l]; - } - } - } - array_copy[Mesh::ARRAY_NORMAL] = narr; - } - if (t.has("TANGENT")) { - const PoolVector tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true); - const PoolVector src_tangents = array[Mesh::ARRAY_TANGENT]; - ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR); - - PoolVector tangents_v4; - - { - - int max_idx = tangents_v3.size(); - - int size4 = src_tangents.size(); - tangents_v4.resize(size4); - const PoolVector::Write w4 = tangents_v4.write(); - - const PoolVector::Read r3 = tangents_v3.read(); - const PoolVector::Read r4 = src_tangents.read(); - - for (int l = 0; l < size4 / 4; l++) { - - if (l < max_idx) { - w4[l * 4 + 0] = r3[l].x + r4[l * 4 + 0]; - w4[l * 4 + 1] = r3[l].y + r4[l * 4 + 1]; - w4[l * 4 + 2] = r3[l].z + r4[l * 4 + 2]; - } else { - w4[l * 4 + 0] = r4[l * 4 + 0]; - w4[l * 4 + 1] = r4[l * 4 + 1]; - w4[l * 4 + 2] = r4[l * 4 + 2]; - } - w4[l * 4 + 3] = r4[l * 4 + 3]; //copy flip value - } - } - - array_copy[Mesh::ARRAY_TANGENT] = tangents_v4; - } - - if (generated_tangents) { - Ref st; - st.instance(); - array_copy[Mesh::ARRAY_INDEX] = erased_indices; //needed for tangent generation, erased by deindex - 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); - - if (p.has("material")) { - const int material = p["material"]; - ERR_FAIL_INDEX_V(material, state.materials.size(), ERR_FILE_CORRUPT); - const Ref &mat = state.materials[material]; - - mesh.mesh->surface_set_material(mesh.mesh->get_surface_count() - 1, mat); - } - } - - if (d.has("weights")) { - const Array &weights = d["weights"]; - ERR_FAIL_COND_V(mesh.mesh->get_blend_shape_count() != weights.size(), ERR_PARSE_ERROR); - mesh.blend_weights.resize(weights.size()); - 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; - - const Array &images = state.json["images"]; - for (int i = 0; i < images.size(); i++) { - - const Dictionary &d = images[i]; - - String mimetype; - if (d.has("mimeType")) { - mimetype = d["mimeType"]; - } - - Vector data; - const uint8_t *data_ptr = NULL; - int data_size = 0; - - if (d.has("uri")) { - String uri = d["uri"]; - - if (uri.findn("data:application/octet-stream;base64") == 0 || - uri.findn("data:" + mimetype + ";base64") == 0) { - //embedded data - data = _parse_base64_uri(uri); - data_ptr = data.ptr(); - data_size = data.size(); - } else { - - uri = p_base_path.plus_file(uri).replace("\\", "/"); //fix for windows - Ref texture = ResourceLoader::load(uri); - state.images.push_back(texture); - continue; - } - } - - if (d.has("bufferView")) { - 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; - } - - ERR_FAIL_COND_V(mimetype == "", ERR_FILE_CORRUPT); - - if (mimetype.findn("png") != -1) { - //is a png - const Ref img = Image::_png_mem_loader_func(data_ptr, data_size); - - ERR_FAIL_COND_V(img.is_null(), ERR_FILE_CORRUPT); - - Ref t; - t.instance(); - t->create_from_image(img); - - state.images.push_back(t); - continue; - } - - if (mimetype.findn("jpeg") != -1) { - //is a jpg - const Ref img = Image::_jpg_mem_loader_func(data_ptr, data_size); - - ERR_FAIL_COND_V(img.is_null(), ERR_FILE_CORRUPT); - - Ref t; - t.instance(); - t->create_from_image(img); - - state.images.push_back(t); - - continue; - } - - ERR_FAIL_V(ERR_FILE_CORRUPT); - } - - print_verbose("Total images: " + itos(state.images.size())); - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_textures(GLTFState &state) { - - if (!state.json.has("textures")) - return OK; - - const Array &textures = state.json["textures"]; - for (GLTFTextureIndex i = 0; i < textures.size(); i++) { - - const Dictionary &d = textures[i]; - - ERR_FAIL_COND_V(!d.has("source"), ERR_PARSE_ERROR); - - GLTFTexture t; - t.src_image = d["source"]; - state.textures.push_back(t); - } - - return OK; -} - -Ref EditorSceneImporterGLTF::_get_texture(GLTFState &state, const GLTFTextureIndex p_texture) { - ERR_FAIL_INDEX_V(p_texture, state.textures.size(), Ref()); - const GLTFImageIndex image = state.textures[p_texture].src_image; - - ERR_FAIL_INDEX_V(image, state.images.size(), Ref()); - - return state.images[image]; -} - -Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { - - if (!state.json.has("materials")) - return OK; - - const Array &materials = state.json["materials"]; - for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { - - const Dictionary &d = materials[i]; - - Ref material; - material.instance(); - if (d.has("name")) { - material->set_name(d["name"]); - } - - if (d.has("pbrMetallicRoughness")) { - - const Dictionary &mr = d["pbrMetallicRoughness"]; - if (mr.has("baseColorFactor")) { - const Array &arr = mr["baseColorFactor"]; - ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); - const Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); - - material->set_albedo(c); - } - - if (mr.has("baseColorTexture")) { - const Dictionary &bct = mr["baseColorTexture"]; - if (bct.has("index")) { - material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, _get_texture(state, bct["index"])); - } - if (!mr.has("baseColorFactor")) { - material->set_albedo(Color(1, 1, 1)); - } - } - - if (mr.has("metallicFactor")) { - material->set_metallic(mr["metallicFactor"]); - } else { - material->set_metallic(1.0); - } - - if (mr.has("roughnessFactor")) { - material->set_roughness(mr["roughnessFactor"]); - } else { - material->set_roughness(1.0); - } - - if (mr.has("metallicRoughnessTexture")) { - const Dictionary &bct = mr["metallicRoughnessTexture"]; - if (bct.has("index")) { - const Ref t = _get_texture(state, bct["index"]); - material->set_texture(SpatialMaterial::TEXTURE_METALLIC, t); - material->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); - material->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, t); - material->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); - if (!mr.has("metallicFactor")) { - material->set_metallic(1); - } - if (!mr.has("roughnessFactor")) { - material->set_roughness(1); - } - } - } - } - - if (d.has("normalTexture")) { - const Dictionary &bct = d["normalTexture"]; - if (bct.has("index")) { - material->set_texture(SpatialMaterial::TEXTURE_NORMAL, _get_texture(state, bct["index"])); - material->set_feature(SpatialMaterial::FEATURE_NORMAL_MAPPING, true); - } - if (bct.has("scale")) { - material->set_normal_scale(bct["scale"]); - } - } - if (d.has("occlusionTexture")) { - const Dictionary &bct = d["occlusionTexture"]; - if (bct.has("index")) { - material->set_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION, _get_texture(state, bct["index"])); - material->set_ao_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_RED); - material->set_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION, true); - } - } - - if (d.has("emissiveFactor")) { - const Array &arr = d["emissiveFactor"]; - ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); - const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); - material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); - - material->set_emission(c); - } - - if (d.has("emissiveTexture")) { - const Dictionary &bct = d["emissiveTexture"]; - if (bct.has("index")) { - material->set_texture(SpatialMaterial::TEXTURE_EMISSION, _get_texture(state, bct["index"])); - material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); - material->set_emission(Color(0, 0, 0)); - } - } - - if (d.has("doubleSided")) { - const bool ds = d["doubleSided"]; - if (ds) { - material->set_cull_mode(SpatialMaterial::CULL_DISABLED); - } - } - - if (d.has("alphaMode")) { - const String &am = d["alphaMode"]; - if (am != "OPAQUE") { - material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - material->set_depth_draw_mode(SpatialMaterial::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - } - - state.materials.push_back(material); - } - - print_verbose("Total materials: " + itos(state.materials.size())); - - return OK; -} - -EditorSceneImporterGLTF::GLTFNodeIndex EditorSceneImporterGLTF::_find_highest_node(GLTFState &state, const Vector &subset) { - int highest = -1; - GLTFNodeIndex best_node = -1; - - for (int i = 0; i < subset.size(); ++i) { - const GLTFNodeIndex node_i = subset[i]; - const GLTFNode *node = state.nodes[node_i]; - - if (highest == -1 || node->height < highest) { - highest = node->height; - best_node = node_i; - } - } - - return best_node; -} - -bool EditorSceneImporterGLTF::_capture_nodes_in_skin(GLTFState &state, GLTFSkin &skin, const GLTFNodeIndex node_index) { - - bool found_joint = false; - - for (int i = 0; i < state.nodes[node_index]->children.size(); ++i) { - found_joint |= _capture_nodes_in_skin(state, skin, state.nodes[node_index]->children[i]); - } - - if (found_joint) { - // Mark it if we happen to find another skins joint... - if (state.nodes[node_index]->joint && skin.joints.find(node_index) < 0) { - skin.joints.push_back(node_index); - } else if (skin.non_joints.find(node_index) < 0) { - skin.non_joints.push_back(node_index); - } - } - - if (skin.joints.find(node_index) > 0) { - return true; - } - - return false; -} - -void EditorSceneImporterGLTF::_capture_nodes_for_multirooted_skin(GLTFState &state, GLTFSkin &skin) { - - DisjointSet disjoint_set; - - for (int i = 0; i < skin.joints.size(); ++i) { - const GLTFNodeIndex node_index = skin.joints[i]; - const GLTFNodeIndex parent = state.nodes[node_index]->parent; - disjoint_set.insert(node_index); - - if (skin.joints.find(parent) >= 0) { - disjoint_set.create_union(parent, node_index); - } - } - - Vector roots; - disjoint_set.get_representatives(roots); - - if (roots.size() <= 1) { - return; - } - - int maxHeight = -1; - - // Determine the max height rooted tree - for (int i = 0; i < roots.size(); ++i) { - const GLTFNodeIndex root = roots[i]; - - if (maxHeight == -1 || state.nodes[root]->height < maxHeight) { - maxHeight = state.nodes[root]->height; - } - } - - // Go up the tree till all of the multiple roots of the skin are at the same hierarchy level. - // This sucks, but 99% of all game engines (not just Godot) would have this same issue. - for (int i = 0; i < roots.size(); ++i) { - - GLTFNodeIndex current_node = roots[i]; - while (state.nodes[current_node]->height > maxHeight) { - GLTFNodeIndex parent = state.nodes[current_node]->parent; - - if (state.nodes[parent]->joint && skin.joints.find(parent) < 0) { - skin.joints.push_back(parent); - } else if (skin.non_joints.find(parent) < 0) { - skin.non_joints.push_back(parent); - } - - current_node = parent; - } - - // replace the roots - roots.write[i] = current_node; - } - - // Climb up the tree until they all have the same parent - bool all_same; - - do { - all_same = true; - const GLTFNodeIndex first_parent = state.nodes[roots[0]]->parent; - - for (int i = 1; i < roots.size(); ++i) { - all_same &= (first_parent == state.nodes[roots[i]]->parent); - } - - if (!all_same) { - for (int i = 0; i < roots.size(); ++i) { - const GLTFNodeIndex current_node = roots[i]; - const GLTFNodeIndex parent = state.nodes[current_node]->parent; - - if (state.nodes[parent]->joint && skin.joints.find(parent) < 0) { - skin.joints.push_back(parent); - } else if (skin.non_joints.find(parent) < 0) { - skin.non_joints.push_back(parent); - } - - roots.write[i] = parent; - } - } - - } while (!all_same); -} - -Error EditorSceneImporterGLTF::_expand_skin(GLTFState &state, GLTFSkin &skin) { - - _capture_nodes_for_multirooted_skin(state, skin); - - // Grab all nodes that lay in between skin joints/nodes - DisjointSet disjoint_set; - - Vector all_skin_nodes; - all_skin_nodes.append_array(skin.joints); - all_skin_nodes.append_array(skin.non_joints); - - for (int i = 0; i < all_skin_nodes.size(); ++i) { - const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state.nodes[node_index]->parent; - disjoint_set.insert(node_index); - - if (all_skin_nodes.find(parent) >= 0) { - disjoint_set.create_union(parent, node_index); - } - } - - Vector out_owners; - disjoint_set.get_representatives(out_owners); - - Vector out_roots; - - for (int i = 0; i < out_owners.size(); ++i) { - Vector set; - disjoint_set.get_members(set, out_owners[i]); - - const GLTFNodeIndex root = _find_highest_node(state, set); - ERR_FAIL_COND_V(root < 0, FAILED); - out_roots.push_back(root); - } - - out_roots.sort(); - - for (int i = 0; i < out_roots.size(); ++i) { - _capture_nodes_in_skin(state, skin, out_roots[i]); - } - - skin.roots = out_roots; - - return OK; -} - -Error EditorSceneImporterGLTF::_verify_skin(GLTFState &state, GLTFSkin &skin) { - - // This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is) - // In case additional interpolating logic is added to the skins, this will help ensure that you - // do not cause it to self implode into a fiery blaze - - // We are going to re-calculate the root nodes and compare them to the ones saved in the skin, - // then ensure the multiple trees (if they exist) are on the same sublevel - - // Grab all nodes that lay in between skin joints/nodes - DisjointSet disjoint_set; - - Vector all_skin_nodes; - all_skin_nodes.append_array(skin.joints); - all_skin_nodes.append_array(skin.non_joints); - - for (int i = 0; i < all_skin_nodes.size(); ++i) { - const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state.nodes[node_index]->parent; - disjoint_set.insert(node_index); - - if (all_skin_nodes.find(parent) >= 0) { - disjoint_set.create_union(parent, node_index); - } - } - - Vector out_owners; - disjoint_set.get_representatives(out_owners); - - Vector out_roots; - - for (int i = 0; i < out_owners.size(); ++i) { - Vector set; - disjoint_set.get_members(set, out_owners[i]); - - const GLTFNodeIndex root = _find_highest_node(state, set); - ERR_FAIL_COND_V(root < 0, FAILED); - out_roots.push_back(root); - } - - out_roots.sort(); - - ERR_FAIL_COND_V(out_roots.size() == 0, FAILED); - - // Make sure the roots are the exact same (they better be) - ERR_FAIL_COND_V(out_roots.size() != skin.roots.size(), FAILED); - for (int i = 0; i < out_roots.size(); ++i) { - ERR_FAIL_COND_V(out_roots[i] != skin.roots[i], FAILED); - } - - // Single rooted skin? Perfectly ok! - if (out_roots.size() == 1) { - return OK; - } - - // Make sure all parents of a multi-rooted skin are the SAME - const GLTFNodeIndex parent = state.nodes[out_roots[0]]->parent; - for (int i = 1; i < out_roots.size(); ++i) { - if (state.nodes[out_roots[i]]->parent != parent) { - return FAILED; - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_parse_skins(GLTFState &state) { - - if (!state.json.has("skins")) - return OK; - - const Array &skins = state.json["skins"]; - - // Create the base skins, and mark nodes that are joints - for (int i = 0; i < skins.size(); i++) { - - const Dictionary &d = skins[i]; - - GLTFSkin skin; - - ERR_FAIL_COND_V(!d.has("joints"), ERR_PARSE_ERROR); - - const Array &joints = d["joints"]; - - if (d.has("inverseBindMatrices")) { - skin.inverse_binds = _decode_accessor_as_xform(state, d["inverseBindMatrices"], false); - ERR_FAIL_COND_V(skin.inverse_binds.size() != joints.size(), ERR_PARSE_ERROR); - } - - for (int j = 0; j < joints.size(); j++) { - const GLTFNodeIndex node = joints[j]; - ERR_FAIL_INDEX_V(node, state.nodes.size(), ERR_PARSE_ERROR); - - skin.joints.push_back(node); - skin.joints_original.push_back(node); - - state.nodes[node]->joint = true; - } - - if (d.has("name")) { - skin.name = d["name"]; - } - - if (d.has("skeleton")) { - skin.skin_root = d["skeleton"]; - } - - state.skins.push_back(skin); - } - - for (GLTFSkinIndex i = 0; i < state.skins.size(); ++i) { - GLTFSkin &skin = state.skins.write[i]; - - // Expand the skin to capture all the extra non-joints that lie in between the actual joints, - // and expand the hierarchy to ensure multi-rooted trees lie on the same height level - ERR_FAIL_COND_V(_expand_skin(state, skin), ERR_PARSE_ERROR); - ERR_FAIL_COND_V(_verify_skin(state, skin), ERR_PARSE_ERROR); - } - - print_verbose("glTF: Total skins: " + itos(state.skins.size())); - - return OK; -} - -Error EditorSceneImporterGLTF::_determine_skeletons(GLTFState &state) { - - // Using a disjoint set, we are going to potentially combine all skins that are actually branches - // of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton. - // This is another unclear issue caused by the current glTF specification. - - DisjointSet skeleton_sets; - - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - const GLTFSkin &skin = state.skins[skin_i]; - - Vector all_skin_nodes; - all_skin_nodes.append_array(skin.joints); - all_skin_nodes.append_array(skin.non_joints); - - for (int i = 0; i < all_skin_nodes.size(); ++i) { - const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state.nodes[node_index]->parent; - skeleton_sets.insert(node_index); - - if (all_skin_nodes.find(parent) >= 0) { - skeleton_sets.create_union(parent, node_index); - } - } - - // We are going to connect the separate skin subtrees in each skin together - // so that the final roots are entire sets of valid skin trees - for (int i = 1; i < skin.roots.size(); ++i) { - skeleton_sets.create_union(skin.roots[0], skin.roots[i]); - } - } - - { // attempt to joint all touching subsets (siblings/parent are part of another skin) - Vector groups_representatives; - skeleton_sets.get_representatives(groups_representatives); - - Vector highest_group_members; - Vector > groups; - for (int i = 0; i < groups_representatives.size(); ++i) { - Vector group; - skeleton_sets.get_members(group, groups_representatives[i]); - highest_group_members.push_back(_find_highest_node(state, group)); - groups.push_back(group); - } - - for (int i = 0; i < highest_group_members.size(); ++i) { - const GLTFNodeIndex node_i = highest_group_members[i]; - - // Attach any siblings together (this needs to be done n^2/2 times) - for (int j = i + 1; j < highest_group_members.size(); ++j) { - const GLTFNodeIndex node_j = highest_group_members[j]; - - // Even if they are siblings under the root! :) - if (state.nodes[node_i]->parent == state.nodes[node_j]->parent) { - skeleton_sets.create_union(node_i, node_j); - } - } - - // Attach any parenting going on together (we need to do this n^2 times) - const GLTFNodeIndex node_i_parent = state.nodes[node_i]->parent; - if (node_i_parent >= 0) { - for (int j = 0; j < groups.size() && i != j; ++j) { - const Vector &group = groups[j]; - - if (group.find(node_i_parent) >= 0) { - const GLTFNodeIndex node_j = highest_group_members[j]; - skeleton_sets.create_union(node_i, node_j); - } - } - } - } - } - - // At this point, the skeleton groups should be finalized - Vector skeleton_owners; - skeleton_sets.get_representatives(skeleton_owners); - - // Mark all the skins actual skeletons, after we have merged them - for (GLTFSkeletonIndex skel_i = 0; skel_i < skeleton_owners.size(); ++skel_i) { - - const GLTFNodeIndex skeleton_owner = skeleton_owners[skel_i]; - GLTFSkeleton skeleton; - - Vector skeleton_nodes; - skeleton_sets.get_members(skeleton_nodes, skeleton_owner); - - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - GLTFSkin &skin = state.skins.write[skin_i]; - - // If any of the the skeletons nodes exist in a skin, that skin now maps to the skeleton - for (int i = 0; i < skeleton_nodes.size(); ++i) { - GLTFNodeIndex skel_node_i = skeleton_nodes[i]; - if (skin.joints.find(skel_node_i) >= 0 || skin.non_joints.find(skel_node_i) >= 0) { - skin.skeleton = skel_i; - continue; - } - } - } - - Vector non_joints; - for (int i = 0; i < skeleton_nodes.size(); ++i) { - const GLTFNodeIndex node_i = skeleton_nodes[i]; - - if (state.nodes[node_i]->joint) { - skeleton.joints.push_back(node_i); - } else { - non_joints.push_back(node_i); - } - } - - state.skeletons.push_back(skeleton); - - _reparent_non_joint_skeleton_subtrees(state, state.skeletons.write[skel_i], non_joints); - } - - for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); ++skel_i) { - GLTFSkeleton &skeleton = state.skeletons.write[skel_i]; - - for (int i = 0; i < skeleton.joints.size(); ++i) { - const GLTFNodeIndex node_i = skeleton.joints[i]; - GLTFNode *node = state.nodes[node_i]; - - ERR_FAIL_COND_V(!node->joint, ERR_PARSE_ERROR); - ERR_FAIL_COND_V(node->skeleton >= 0, ERR_PARSE_ERROR); - node->skeleton = skel_i; - } - - ERR_FAIL_COND_V(_determine_skeleton_roots(state, skel_i), ERR_PARSE_ERROR); - } - - return OK; -} - -Error EditorSceneImporterGLTF::_reparent_non_joint_skeleton_subtrees(GLTFState &state, GLTFSkeleton &skeleton, const Vector &non_joints) { - - DisjointSet subtree_set; - - // Populate the disjoint set with ONLY non joints that are in the skeleton hierarchy (non_joints vector) - // This way we can find any joints that lie in between joints, as the current glTF specification - // mentions nothing about non-joints being in between joints of the same skin. Hopefully one day we - // can remove this code. - - // skinD depicted here explains this issue: - // https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Animation_Skin - - for (int i = 0; i < non_joints.size(); ++i) { - const GLTFNodeIndex node_i = non_joints[i]; - - subtree_set.insert(node_i); - - const GLTFNodeIndex parent_i = state.nodes[node_i]->parent; - if (parent_i >= 0 && non_joints.find(parent_i) >= 0 && !state.nodes[parent_i]->joint) { - subtree_set.create_union(parent_i, node_i); - } - } - - // Find all the non joint subtrees and re-parent them to a new "fake" joint - - Vector non_joint_subtree_roots; - subtree_set.get_representatives(non_joint_subtree_roots); - - for (int root_i = 0; root_i < non_joint_subtree_roots.size(); ++root_i) { - const GLTFNodeIndex subtree_root = non_joint_subtree_roots[root_i]; - - Vector subtree_nodes; - subtree_set.get_members(subtree_nodes, subtree_root); - - for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) { - ERR_FAIL_COND_V(_reparent_to_fake_joint(state, skeleton, subtree_nodes[subtree_i]), FAILED); - - // We modified the tree, recompute all the heights - _compute_node_heights(state); - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_reparent_to_fake_joint(GLTFState &state, GLTFSkeleton &skeleton, const GLTFNodeIndex node_index) { - GLTFNode *node = state.nodes[node_index]; - - // Can we just "steal" this joint if it is just a spatial node? - if (node->skin < 0 && node->mesh < 0 && node->camera < 0) { - node->joint = true; - // Add the joint to the skeletons joints - skeleton.joints.push_back(node_index); - return OK; - } - - GLTFNode *fake_joint = memnew(GLTFNode); - const GLTFNodeIndex fake_joint_index = state.nodes.size(); - state.nodes.push_back(fake_joint); - - // We better not be a joint, or we messed up in our logic - if (node->joint) - return FAILED; - - fake_joint->translation = node->translation; - fake_joint->rotation = node->rotation; - fake_joint->scale = node->scale; - fake_joint->xform = node->xform; - fake_joint->joint = true; - - // We can use the exact same name here, because the joint will be inside a skeleton and not the scene - fake_joint->name = node->name; - - // Clear the nodes transforms, since it will be parented to the fake joint - node->translation = Vector3(0, 0, 0); - node->rotation = Quat(); - node->scale = Vector3(1, 1, 1); - node->xform = Transform(); - - // Transfer the node children to the fake joint - for (int child_i = 0; child_i < node->children.size(); ++child_i) { - GLTFNode *child = state.nodes[node->children[child_i]]; - child->parent = fake_joint_index; - } - - fake_joint->children = node->children; - node->children.clear(); - - // add the fake joint to the parent and remove the original joint - if (node->parent >= 0) { - GLTFNode *parent = state.nodes[node->parent]; - parent->children.erase(node_index); - parent->children.push_back(fake_joint_index); - fake_joint->parent = node->parent; - } - - // Add the node to the fake joint - fake_joint->children.push_back(node_index); - node->parent = fake_joint_index; - node->fake_joint_parent = fake_joint_index; - - // Add the fake joint to the skeletons joints - skeleton.joints.push_back(fake_joint_index); - - // Replace skin_skeletons with fake joints if we must. - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - GLTFSkin &skin = state.skins.write[skin_i]; - if (skin.skin_root == node_index) { - skin.skin_root = fake_joint_index; - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_determine_skeleton_roots(GLTFState &state, const GLTFSkeletonIndex skel_i) { - - DisjointSet disjoint_set; - - for (GLTFNodeIndex i = 0; i < state.nodes.size(); ++i) { - const GLTFNode *node = state.nodes[i]; - - if (node->skeleton != skel_i) { - continue; - } - - disjoint_set.insert(i); - - if (node->parent >= 0 && state.nodes[node->parent]->skeleton == skel_i) { - disjoint_set.create_union(node->parent, i); - } - } - - GLTFSkeleton &skeleton = state.skeletons.write[skel_i]; - - Vector owners; - disjoint_set.get_representatives(owners); - - Vector roots; - - for (int i = 0; i < owners.size(); ++i) { - Vector set; - disjoint_set.get_members(set, owners[i]); - const GLTFNodeIndex root = _find_highest_node(state, set); - ERR_FAIL_COND_V(root < 0, FAILED); - roots.push_back(root); - } - - roots.sort(); - - skeleton.roots = roots; - - if (roots.size() == 0) { - return FAILED; - } else if (roots.size() == 1) { - return OK; - } - - // Check that the subtrees have the same parent root - const GLTFNodeIndex parent = state.nodes[roots[0]]->parent; - for (int i = 1; i < roots.size(); ++i) { - if (state.nodes[roots[i]]->parent != parent) { - return FAILED; - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_create_skeletons(GLTFState &state) { - for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); ++skel_i) { - - GLTFSkeleton &gltf_skeleton = state.skeletons.write[skel_i]; - - Skeleton *skeleton = memnew(Skeleton); - gltf_skeleton.godot_skeleton = skeleton; - - // Make a unique name, no gltf node represents this skeleton - skeleton->set_name(_gen_unique_name(state, "Skeleton")); - - List bones; - - for (int i = 0; i < gltf_skeleton.roots.size(); ++i) { - bones.push_back(gltf_skeleton.roots[i]); - } - - // Make the skeleton creation deterministic by going through the roots in - // a sorted order, and DEPTH FIRST - bones.sort(); - - while (!bones.empty()) { - const GLTFNodeIndex node_i = bones.front()->get(); - bones.pop_front(); - - GLTFNode *node = state.nodes[node_i]; - ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED); - - { // Add all child nodes to the stack (deterministically) - Vector child_nodes; - for (int i = 0; i < node->children.size(); ++i) { - const GLTFNodeIndex child_i = node->children[i]; - if (state.nodes[child_i]->skeleton == skel_i) { - child_nodes.push_back(child_i); - } - } - - // Depth first insertion - child_nodes.sort(); - for (int i = child_nodes.size() - 1; i >= 0; --i) { - bones.push_front(child_nodes[i]); - } - } - - const int bone_index = skeleton->get_bone_count(); - - if (node->name.empty()) { - node->name = "bone"; - } - - node->name = _gen_unique_bone_name(state, skel_i, node->name); - - skeleton->add_bone(node->name); - skeleton->set_bone_rest(bone_index, node->xform); - - if (node->parent >= 0 && state.nodes[node->parent]->skeleton == skel_i) { - const int bone_parent = skeleton->find_bone(state.nodes[node->parent]->name); - ERR_FAIL_COND_V(bone_parent < 0, FAILED); - skeleton->set_bone_parent(bone_index, skeleton->find_bone(state.nodes[node->parent]->name)); - } - - state.scene_nodes.insert(node_i, skeleton); - } - } - - ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(state), ERR_PARSE_ERROR); - - return OK; -} - -Error EditorSceneImporterGLTF::_map_skin_joints_indices_to_skeleton_bone_indices(GLTFState &state) { - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - GLTFSkin &skin = state.skins.write[skin_i]; - - const GLTFSkeleton &skeleton = state.skeletons[skin.skeleton]; - - for (int joint_index = 0; joint_index < skin.joints_original.size(); ++joint_index) { - const GLTFNodeIndex node_i = skin.joints_original[joint_index]; - const GLTFNode *node = state.nodes[node_i]; - - const int bone_index = skeleton.godot_skeleton->find_bone(node->name); - ERR_FAIL_COND_V(bone_index < 0, FAILED); - - skin.joint_i_to_bone_i.insert(joint_index, bone_index); - } - } - - return OK; -} - -Error EditorSceneImporterGLTF::_create_skins(GLTFState &state) { - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - GLTFSkin &gltf_skin = state.skins.write[skin_i]; - - Ref skin; - skin.instance(); - - // Some skins don't have IBM's! What absolute monsters! - const bool has_ibms = !gltf_skin.inverse_binds.empty(); - - for (int joint_i = 0; joint_i < gltf_skin.joints_original.size(); ++joint_i) { - int bone_i = gltf_skin.joint_i_to_bone_i[joint_i]; - - if (has_ibms) { - skin->add_bind(bone_i, gltf_skin.inverse_binds[joint_i]); - } else { - skin->add_bind(bone_i, Transform()); - } - } - - gltf_skin.godot_skin = skin; - } - - // Purge the duplicates! - _remove_duplicate_skins(state); - - // Create unique names now, after removing duplicates - for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { - Ref skin = state.skins[skin_i].godot_skin; - if (skin->get_name().empty()) { - // Make a unique name, no gltf node represents this skin - skin->set_name(_gen_unique_name(state, "Skin")); - } - } - - return OK; -} - -bool EditorSceneImporterGLTF::_skins_are_same(const Ref &skin_a, const Ref &skin_b) { - if (skin_a->get_bind_count() != skin_b->get_bind_count()) { - return false; - } - - for (int i = 0; i < skin_a->get_bind_count(); ++i) { - - if (skin_a->get_bind_bone(i) != skin_b->get_bind_bone(i)) { - return false; - } - - Transform a_xform = skin_a->get_bind_pose(i); - Transform b_xform = skin_b->get_bind_pose(i); - - if (a_xform != b_xform) { - return false; - } - } - - return true; -} - -void EditorSceneImporterGLTF::_remove_duplicate_skins(GLTFState &state) { - for (int i = 0; i < state.skins.size(); ++i) { - for (int j = i + 1; j < state.skins.size(); ++j) { - const Ref &skin_i = state.skins[i].godot_skin; - const Ref &skin_j = state.skins[j].godot_skin; - - if (_skins_are_same(skin_i, skin_j)) { - // replace it and delete the old - state.skins.write[j].godot_skin = skin_i; - } - } - } -} - -Error EditorSceneImporterGLTF::_parse_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")) { - animation.name = _sanitize_scene_name(d["name"]); - } - - for (int j = 0; j < channels.size(); j++) { - - const Dictionary &c = channels[j]; - if (!c.has("target")) - continue; - - const Dictionary &t = c["target"]; - if (!t.has("node") || !t.has("path")) { - continue; - } - - ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR); - const int sampler = c["sampler"]; - ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR); - - GLTFNodeIndex node = t["node"]; - String path = t["path"]; - - ERR_FAIL_INDEX_V(node, state.nodes.size(), ERR_PARSE_ERROR); - - GLTFAnimation::Track *track = nullptr; - - if (!animation.tracks.has(node)) { - animation.tracks[node] = GLTFAnimation::Track(); - } - - track = &animation.tracks[node]; - - const Dictionary &s = samplers[sampler]; - - ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR); - ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR); - - const int input = s["input"]; - const int output = s["output"]; - - GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR; - int output_count = 1; - if (s.has("interpolation")) { - const String &in = s["interpolation"]; - if (in == "STEP") { - interp = GLTFAnimation::INTERP_STEP; - } else if (in == "LINEAR") { - interp = GLTFAnimation::INTERP_LINEAR; - } else if (in == "CATMULLROMSPLINE") { - interp = GLTFAnimation::INTERP_CATMULLROMSPLINE; - output_count = 3; - } else if (in == "CUBICSPLINE") { - interp = GLTFAnimation::INTERP_CUBIC_SPLINE; - output_count = 3; - } - } - - const PoolVector times = _decode_accessor_as_floats(state, input, false); - if (path == "translation") { - const PoolVector translations = _decode_accessor_as_vec3(state, output, false); - track->translation_track.interpolation = interp; - track->translation_track.times = Variant(times); //convert via variant - track->translation_track.values = Variant(translations); //convert via variant - } else if (path == "rotation") { - const Vector rotations = _decode_accessor_as_quat(state, output, false); - track->rotation_track.interpolation = interp; - track->rotation_track.times = Variant(times); //convert via variant - track->rotation_track.values = rotations; //convert via variant - } else if (path == "scale") { - const PoolVector scales = _decode_accessor_as_vec3(state, output, false); - track->scale_track.interpolation = interp; - track->scale_track.times = Variant(times); //convert via variant - track->scale_track.values = Variant(scales); //convert via variant - } else if (path == "weights") { - const PoolVector weights = _decode_accessor_as_floats(state, output, false); - - ERR_FAIL_INDEX_V(state.nodes[node]->mesh, state.meshes.size(), ERR_PARSE_ERROR); - const GLTFMesh *mesh = &state.meshes[state.nodes[node]->mesh]; - ERR_FAIL_COND_V(mesh->blend_weights.size() == 0, ERR_PARSE_ERROR); - const int wc = mesh->blend_weights.size(); - - track->weight_tracks.resize(wc); - - const int expected_value_count = times.size() * output_count * wc; - ERR_FAIL_COND_V_MSG(weights.size() != expected_value_count, ERR_PARSE_ERROR, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead."); - - const int wlen = weights.size() / wc; - PoolVector::Read r = weights.read(); - for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea - GLTFAnimation::Channel cf; - cf.interpolation = interp; - cf.times = Variant(times); - Vector wdata; - wdata.resize(wlen); - for (int l = 0; l < wlen; l++) { - wdata.write[l] = r[l * wc + k]; - } - - cf.values = wdata; - track->weight_tracks.write[k] = cf; - } - } else { - WARN_PRINTS("Invalid path '" + path + "'."); - } - } - - state.animations.push_back(animation); - } - - print_verbose("glTF: Total animations '" + itos(state.animations.size()) + "'."); - - return OK; -} - -void EditorSceneImporterGLTF::_assign_scene_names(GLTFState &state) { - - for (int i = 0; i < state.nodes.size(); i++) { - GLTFNode *n = state.nodes[i]; - - // Any joints get unique names generated when the skeleton is made, unique to the skeleton - if (n->skeleton >= 0) - continue; - - if (n->name.empty()) { - if (n->mesh >= 0) { - n->name = "Mesh"; - } else if (n->camera >= 0) { - n->name = "Camera"; - } else { - n->name = "Node"; - } - } - - n->name = _gen_unique_name(state, n->name); - } -} - -BoneAttachment *EditorSceneImporterGLTF::_generate_bone_attachment(GLTFState &state, Skeleton *skeleton, const GLTFNodeIndex node_index) { - - const GLTFNode *gltf_node = state.nodes[node_index]; - const GLTFNode *bone_node = state.nodes[gltf_node->parent]; - - 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; -} - -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]; - - Spatial *current_node = nullptr; - - // Is our parent a skeleton - Skeleton *active_skeleton = Object::cast_to(scene_parent); - - if (gltf_node->skeleton >= 0) { - Skeleton *skeleton = state.skeletons[gltf_node->skeleton].godot_skeleton; - - if (active_skeleton != skeleton) { - ERR_FAIL_COND_MSG(active_skeleton != nullptr, "glTF: Generating scene detected direct parented Skeletons"); - - // Add it to the scene if it has not already been added - if (skeleton->get_parent() == nullptr) { - scene_parent->add_child(skeleton); - skeleton->set_owner(scene_root); - } - } - - active_skeleton = skeleton; - current_node = skeleton; - } - - // If we have an active skeleton, and the node is node skinned, we need to create a bone attachment - if (current_node == nullptr && active_skeleton != nullptr && gltf_node->skin < 0) { - BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index); - - scene_parent->add_child(bone_attachment); - bone_attachment->set_owner(scene_root); - - // There is no gltf_node that represent this, so just directly create a unique name - bone_attachment->set_name(_gen_unique_name(state, "BoneAttachment")); - - // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node - // and attach it to the bone_attachment - scene_parent = bone_attachment; - } - - // We still have not managed to make a node - if (current_node == nullptr) { - 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 { - 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); - 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]); - } -} - -template -struct EditorSceneImporterGLTFInterpolate { - - T lerp(const T &a, const T &b, float c) const { - - return a + (b - a) * c; - } - - T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) { - - const float t2 = t * t; - const float t3 = t2 * t; - - return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); - } - - T bezier(T start, T control_1, T control_2, T end, float t) { - /* Formula from Wikipedia article on Bezier curves. */ - const real_t omt = (1.0 - t); - const real_t omt2 = omt * omt; - const real_t omt3 = omt2 * omt; - const real_t t2 = t * t; - const real_t t3 = t2 * t; - - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; - } -}; - -// thank you for existing, partial specialization -template <> -struct EditorSceneImporterGLTFInterpolate { - - Quat lerp(const Quat &a, const Quat &b, const float c) const { - ERR_FAIL_COND_V(!a.is_normalized(), Quat()); - ERR_FAIL_COND_V(!b.is_normalized(), Quat()); - - 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(!p1.is_normalized(), Quat()); - ERR_FAIL_COND_V(!p2.is_normalized(), Quat()); - - 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(!start.is_normalized(), Quat()); - ERR_FAIL_COND_V(!end.is_normalized(), Quat()); - - return start.slerp(end, t).normalized(); - } -}; - -template -T EditorSceneImporterGLTF::_interpolate_track(const Vector &p_times, const Vector &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { - - //could use binary search, worth it? - int idx = -1; - for (int i = 0; i < p_times.size(); i++) { - if (p_times[i] > p_time) - break; - idx++; - } - - EditorSceneImporterGLTFInterpolate interp; - - switch (p_interp) { - case GLTFAnimation::INTERP_LINEAR: { - - if (idx == -1) { - return p_values[0]; - } else if (idx >= p_times.size() - 1) { - return p_values[p_times.size() - 1]; - } - - const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - return interp.lerp(p_values[idx], p_values[idx + 1], c); - - } break; - case GLTFAnimation::INTERP_STEP: { - - if (idx == -1) { - return p_values[0]; - } else if (idx >= p_times.size() - 1) { - return p_values[p_times.size() - 1]; - } - - return p_values[idx]; - - } break; - case GLTFAnimation::INTERP_CATMULLROMSPLINE: { - - if (idx == -1) { - return p_values[1]; - } else if (idx >= p_times.size() - 1) { - return p_values[1 + p_times.size() - 1]; - } - - const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - return interp.catmull_rom(p_values[idx - 1], p_values[idx], p_values[idx + 1], p_values[idx + 3], c); - - } break; - case GLTFAnimation::INTERP_CUBIC_SPLINE: { - - if (idx == -1) { - return p_values[1]; - } else if (idx >= p_times.size() - 1) { - return p_values[(p_times.size() - 1) * 3 + 1]; - } - - const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - const T from = p_values[idx * 3 + 1]; - const T c1 = from + p_values[idx * 3 + 2]; - const T to = p_values[idx * 3 + 4]; - const T c2 = to + p_values[idx * 3 + 3]; - - return interp.bezier(from, c1, c2, to, c); - - } break; - } - - ERR_FAIL_V(p_values[0]); -} - -void EditorSceneImporterGLTF::_import_animation(GLTFState &state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) { - - const GLTFAnimation &anim = state.animations[index]; - - String name = anim.name; - if (name.empty()) { - // No node represent these, and they are not in the hierarchy, so just make a unique name - name = _gen_unique_name(state, "Animation"); - } - - Ref animation; - animation.instance(); - animation->set_name(name); - - float length = 0; - - for (Map::Element *E = anim.tracks.front(); E; E = E->next()) { - - const GLTFAnimation::Track &track = E->get(); - //need to find the path - NodePath node_path; - - GLTFNodeIndex node_index = E->key(); - if (state.nodes[node_index]->fake_joint_parent >= 0) { - // Should be same as parent - node_index = state.nodes[node_index]->fake_joint_parent; - } - - const GLTFNode *node = state.nodes[E->key()]; - - if (node->skeleton >= 0) { - const Skeleton *sk = Object::cast_to(state.scene_nodes.find(node_index)->get()); - ERR_FAIL_COND(sk == nullptr); - - const String path = ap->get_parent()->get_path_to(sk); - const String bone = node->name; - node_path = path + ":" + bone; - } else { - node_path = ap->get_parent()->get_path_to(state.scene_nodes.find(node_index)->get()); - } - - for (int i = 0; i < track.rotation_track.times.size(); i++) { - length = MAX(length, track.rotation_track.times[i]); - } - for (int i = 0; i < track.translation_track.times.size(); i++) { - length = MAX(length, track.translation_track.times[i]); - } - for (int i = 0; i < track.scale_track.times.size(); i++) { - length = MAX(length, track.scale_track.times[i]); - } - - for (int i = 0; i < track.weight_tracks.size(); i++) { - for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { - length = MAX(length, track.weight_tracks[i].times[j]); - } - } - - if (track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) { - //make transform track - int track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_TRANSFORM); - animation->track_set_path(track_idx, node_path); - //first determine animation length - - const float increment = 1.0 / float(bake_fps); - float time = 0.0; - - Vector3 base_pos; - Quat base_rot; - Vector3 base_scale = Vector3(1, 1, 1); - - if (!track.rotation_track.values.size()) { - base_rot = state.nodes[E->key()]->rotation.normalized(); - } - - if (!track.translation_track.values.size()) { - base_pos = state.nodes[E->key()]->translation; - } - - if (!track.scale_track.values.size()) { - base_scale = state.nodes[E->key()]->scale; - } - - bool last = false; - while (true) { - - Vector3 pos = base_pos; - Quat rot = base_rot; - Vector3 scale = base_scale; - - if (track.translation_track.times.size()) { - pos = _interpolate_track(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation); - } - - if (track.rotation_track.times.size()) { - rot = _interpolate_track(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); - } - - if (track.scale_track.times.size()) { - scale = _interpolate_track(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); - } - - if (node->skeleton >= 0) { - - Transform xform; - xform.basis.set_quat_scale(rot, scale); - xform.origin = pos; - - const Skeleton *skeleton = state.skeletons[node->skeleton].godot_skeleton; - const int bone_idx = skeleton->find_bone(node->name); - xform = skeleton->get_bone_rest(bone_idx).affine_inverse() * xform; - - rot = xform.basis.get_rotation_quat(); - rot.normalize(); - scale = xform.basis.get_scale(); - pos = xform.origin; - } - - animation->transform_track_insert_key(track_idx, time, pos, rot, scale); - - if (last) { - break; - } - time += increment; - if (time >= length) { - last = true; - time = length; - } - } - } - - for (int i = 0; i < track.weight_tracks.size(); i++) { - ERR_CONTINUE(node->mesh < 0 || node->mesh >= state.meshes.size()); - const GLTFMesh &mesh = state.meshes[node->mesh]; - const String prop = "blend_shapes/" + mesh.mesh->get_blend_shape_name(i); - - const String blend_path = String(node_path) + ":" + prop; - - const int track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_VALUE); - animation->track_set_path(track_idx, blend_path); - - // Only LINEAR and STEP (NEAREST) can be supported out of the box by Godot's Animation, - // the other modes have to be baked. - GLTFAnimation::Interpolation gltf_interp = track.weight_tracks[i].interpolation; - if (gltf_interp == GLTFAnimation::INTERP_LINEAR || gltf_interp == GLTFAnimation::INTERP_STEP) { - animation->track_set_interpolation_type(track_idx, gltf_interp == GLTFAnimation::INTERP_STEP ? Animation::INTERPOLATION_NEAREST : Animation::INTERPOLATION_LINEAR); - for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { - const float t = track.weight_tracks[i].times[j]; - const float w = track.weight_tracks[i].values[j]; - animation->track_insert_key(track_idx, t, w); - } - } else { - // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const float increment = 1.0 / float(bake_fps); - float time = 0.0; - bool last = false; - while (true) { - _interpolate_track(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); - if (last) { - break; - } - time += increment; - if (time >= length) { - last = true; - time = length; - } - } - } - } - } - - animation->set_length(length); - - ap->add_animation(name, animation); -} - -void EditorSceneImporterGLTF::_process_mesh_instances(GLTFState &state, Spatial *scene_root) { - for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { - const GLTFNode *node = state.nodes[node_i]; - - if (node->skin >= 0 && node->mesh >= 0) { - const GLTFSkinIndex skin_i = node->skin; - - Map::Element *mi_element = state.scene_nodes.find(node_i); - MeshInstance *mi = Object::cast_to(mi_element->get()); - ERR_FAIL_COND(mi == nullptr); - - const GLTFSkeletonIndex skel_i = state.skins[node->skin].skeleton; - const GLTFSkeleton &gltf_skeleton = state.skeletons[skel_i]; - Skeleton *skeleton = gltf_skeleton.godot_skeleton; - ERR_FAIL_COND(skeleton == nullptr); - - 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 - root->set_name(state.scene_name); - - for (int i = 0; i < state.root_nodes.size(); ++i) { - _generate_scene_node(state, root, root, state.root_nodes[i]); - } - - _process_mesh_instances(state, root); - - if (state.animations.size()) { - AnimationPlayer *ap = memnew(AnimationPlayer); - ap->set_name("AnimationPlayer"); - root->add_child(ap); - ap->set_owner(root); - - for (int i = 0; i < state.animations.size(); i++) { - _import_animation(state, ap, i, p_bake_fps); - } - } - - return root; -} - -Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err) { - - GLTFState state; - - if (p_path.to_lower().ends_with("glb")) { - //binary file - //text file - Error err = _parse_glb(p_path, state); - if (err) - return NULL; - } else { - //text file - Error err = _parse_json(p_path, state); - if (err) - return NULL; - } - - ERR_FAIL_COND_V(!state.json.has("asset"), NULL); - - Dictionary asset = state.json["asset"]; - - ERR_FAIL_COND_V(!asset.has("version"), NULL); - - 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 */ - Error err = _parse_scenes(state); - if (err != OK) - return NULL; - - /* STEP 1 PARSE NODES */ - err = _parse_nodes(state); - if (err != OK) - return NULL; - - /* STEP 2 PARSE BUFFERS */ - err = _parse_buffers(state, p_path.get_base_dir()); - if (err != OK) - return NULL; - - /* STEP 3 PARSE BUFFER VIEWS */ - err = _parse_buffer_views(state); - if (err != OK) - return NULL; - - /* STEP 4 PARSE ACCESSORS */ - err = _parse_accessors(state); - if (err != OK) - return NULL; - - /* STEP 5 PARSE IMAGES */ - err = _parse_images(state, p_path.get_base_dir()); - if (err != OK) - return NULL; - - /* STEP 6 PARSE TEXTURES */ - err = _parse_textures(state); - if (err != OK) - return NULL; - - /* STEP 7 PARSE TEXTURES */ - err = _parse_materials(state); - if (err != OK) - return NULL; - - /* STEP 9 PARSE SKINS */ - err = _parse_skins(state); - if (err != OK) - return NULL; - - /* STEP 10 DETERMINE SKELETONS */ - err = _determine_skeletons(state); - if (err != OK) - return NULL; - - /* STEP 11 CREATE SKELETONS */ - err = _create_skeletons(state); - if (err != OK) - return NULL; - - /* STEP 12 CREATE SKINS */ - err = _create_skins(state); - if (err != OK) - return NULL; - - /* STEP 13 PARSE MESHES (we have enough info now) */ - err = _parse_meshes(state); - if (err != OK) - return NULL; - - /* STEP 14 PARSE CAMERAS */ - err = _parse_cameras(state); - if (err != OK) - return NULL; - - /* STEP 15 PARSE ANIMATIONS */ - err = _parse_animations(state); - if (err != OK) - return NULL; - - /* STEP 16 ASSIGN SCENE NAMES */ - _assign_scene_names(state); - - /* STEP 17 MAKE SCENE! */ - Spatial *scene = _generate_scene(state, p_bake_fps); - - return scene; -} - -Ref EditorSceneImporterGLTF::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { - - return Ref(); -} - -EditorSceneImporterGLTF::EditorSceneImporterGLTF() { -} diff --git a/editor/saver/SCsub b/editor/saver/SCsub new file mode 100644 index 000000000000..2b1e889fb03b --- /dev/null +++ b/editor/saver/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import('env') + +env.add_source_files(env.editor_sources, "*.cpp") diff --git a/editor/saver/editor_saver_plugin.cpp b/editor/saver/editor_saver_plugin.cpp new file mode 100644 index 000000000000..ab4eaf498659 --- /dev/null +++ b/editor/saver/editor_saver_plugin.cpp @@ -0,0 +1,169 @@ +/*************************************************************************/ +/* editor_import_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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_saver_plugin.h" +#include "core/script_language.h" + +EditorSaverPlugin::EditorSaverPlugin() { +} + +// String EditorSaverPlugin::get_importer_name() const { +// ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("get_saver_name")), ""); +// return get_script_instance()->call("get_saver_name"); +// } + +// String EditorSaverPlugin::get_visible_name() const { +// ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("get_visible_name")), ""); +// return get_script_instance()->call("get_visible_name"); +// } + +// void EditorSaverPlugin::get_recognized_extensions(List *p_extensions) const { +// ERR_FAIL_COND(!(get_script_instance() && get_script_instance()->has_method("get_recognized_extensions"))); +// Array extensions = get_script_instance()->call("get_recognized_extensions"); +// for (int i = 0; i < extensions.size(); i++) { +// p_extensions->push_back(extensions[i]); +// } +// } + +// String EditorSaverPlugin::get_preset_name(int p_idx) const { +// ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("get_preset_name")), ""); +// return get_script_instance()->call("get_preset_name", p_idx); +// } + +// int EditorSaverPlugin::get_preset_count() const { +// ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("get_preset_count")), 0); +// return get_script_instance()->call("get_preset_count"); +// } + +// String EditorSaverPlugin::get_save_extension() const { +// ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("get_save_extension")), ""); +// return get_script_instance()->call("get_save_extension"); +// } + +// String EditorSaverPlugin::get_resource_type() const { +// ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("get_resource_type")), ""); +// return get_script_instance()->call("get_resource_type"); +// } + +// float EditorSaverPlugin::get_priority() const { +// if (!(get_script_instance() && get_script_instance()->has_method("get_priority"))) { +// return ResourceImporter::get_priority(); +// } +// return get_script_instance()->call("get_priority"); +// } + +// int EditorSaverPlugin::get_import_order() const { +// if (!(get_script_instance() && get_script_instance()->has_method("get_import_order"))) { +// return ResourceImporter::get_import_order(); +// } +// return get_script_instance()->call("get_import_order"); +// } + +// void EditorSaverPlugin::get_import_options(List *r_options, int p_preset) const { + +// ERR_FAIL_COND(!(get_script_instance() && get_script_instance()->has_method("get_import_options"))); +// Array needed; +// needed.push_back("name"); +// needed.push_back("default_value"); +// Array options = get_script_instance()->call("get_import_options", p_preset); +// for (int i = 0; i < options.size(); i++) { +// Dictionary d = options[i]; +// ERR_FAIL_COND(!d.has_all(needed)); +// String name = d["name"]; +// Variant default_value = d["default_value"]; + +// PropertyHint hint = PROPERTY_HINT_NONE; +// if (d.has("property_hint")) { +// hint = (PropertyHint)d["property_hint"].operator int64_t(); +// } + +// String hint_string; +// if (d.has("hint_string")) { +// hint_string = d["hint_string"]; +// } + +// uint32_t usage = PROPERTY_USAGE_DEFAULT; +// if (d.has("usage")) { +// usage = d["usage"]; +// } + +// ImportOption option(PropertyInfo(default_value.get_type(), name, hint, hint_string, usage), default_value); +// r_options->push_back(option); +// } +// } + +// bool EditorSaverPlugin::get_option_visibility(const String &p_option, const Map &p_options) const { +// ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("get_option_visibility")), true); +// Dictionary d; +// Map::Element *E = p_options.front(); +// while (E) { +// d[E->key()] = E->get(); +// E = E->next(); +// } +// return get_script_instance()->call("get_option_visibility", p_option, d); +// } + +Error EditorSaverPlugin::save(const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata) { + + ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("import")), ERR_UNAVAILABLE); + Dictionary options; + Array platform_variants, gen_files; + + Map::Element *E = p_options.front(); + while (E) { + options[E->key()] = E->get(); + E = E->next(); + } + Error err = (Error)get_script_instance()->call("import", p_source_file, p_save_path, options, platform_variants, gen_files).operator int64_t(); + + for (int i = 0; i < platform_variants.size(); i++) { + r_platform_variants->push_back(platform_variants[i]); + } + for (int i = 0; i < gen_files.size(); i++) { + r_gen_files->push_back(gen_files[i]); + } + return err; +} + +void EditorSaverPlugin::_bind_methods() { + + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::STRING, "get_importer_name")); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::STRING, "get_visible_name")); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::INT, "get_preset_count")); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::STRING, "get_preset_name", PropertyInfo(Variant::INT, "preset"))); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::ARRAY, "get_recognized_extensions")); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::ARRAY, "get_import_options", PropertyInfo(Variant::INT, "preset"))); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::STRING, "get_save_extension")); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::STRING, "get_resource_type")); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::REAL, "get_priority")); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::INT, "get_import_order")); + // ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::BOOL, "get_option_visibility", PropertyInfo(Variant::STRING, "option"), PropertyInfo(Variant::DICTIONARY, "options"))); + ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::INT, "save", PropertyInfo(Variant::STRING, "source_file"), PropertyInfo(Variant::STRING, "save_path"), PropertyInfo(Variant::DICTIONARY, "options"), PropertyInfo(Variant::ARRAY, "platform_variants"), PropertyInfo(Variant::ARRAY, "gen_files"))); +} diff --git a/editor/saver/editor_saver_plugin.h b/editor/saver/editor_saver_plugin.h new file mode 100644 index 000000000000..55c77b5a1016 --- /dev/null +++ b/editor/saver/editor_saver_plugin.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* editor_saver_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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_IMPORT_PLUGIN_H +#define EDITOR_IMPORT_PLUGIN_H + +#include "core/io/resource_saver.h" + +class EditorSaverPlugin : public ResourceSaver { + GDCLASS(EditorSaverPlugin, ResourceSaver); + +protected: + static void _bind_methods(); + +public: + EditorSaverPlugin(); + // virtual String get_importer_name() const; + // virtual String get_visible_name() const; + // virtual void get_recognized_extensions(List *p_extensions) const; + // virtual String get_preset_name(int p_idx) const; + // virtual int get_preset_count() const; + // virtual String get_save_extension() const; + // virtual String get_resource_type() const; + // virtual float get_priority() const; + // virtual int get_import_order() const; + // virtual void get_import_options(List *r_options, int p_preset) const; + // virtual bool get_option_visibility(const String &p_option, const Map &p_options) const; + virtual Error save(const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata = NULL); +}; + +#endif //EDITOR_IMPORT_PLUGIN_H diff --git a/editor/saver/resource_saver_scene.cpp b/editor/saver/resource_saver_scene.cpp new file mode 100644 index 000000000000..d3988c9f2151 --- /dev/null +++ b/editor/saver/resource_saver_scene.cpp @@ -0,0 +1,793 @@ +/*************************************************************************/ +/* resource_importer_scene.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 "resource_saver_scene.h" + +#include "core/io/resource_saver.h" +#include "editor/editor_node.h" +#include "scene/resources/packed_scene.h" + +#include "scene/3d/collision_shape.h" +#include "scene/3d/mesh_instance.h" +#include "scene/3d/navigation.h" +#include "scene/3d/physics_body.h" +#include "scene/3d/portal.h" +#include "scene/3d/room_instance.h" +#include "scene/3d/vehicle_body.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/animation.h" +#include "scene/resources/box_shape.h" +#include "scene/resources/plane_shape.h" +#include "scene/resources/ray_shape.h" +#include "scene/resources/resource_format_text.h" +#include "scene/resources/sphere_shape.h" + +uint32_t EditorSceneExporter::get_save_flags() const { + + if (get_script_instance()) { + return get_script_instance()->call("_get_import_flags"); + } + + ERR_FAIL_V(0); +} +void EditorSceneExporter::get_exporter_extensions(List *r_extensions) const { + + if (get_script_instance()) { + Array arr = get_script_instance()->call("_get_extensions"); + for (int i = 0; i < arr.size(); i++) { + r_extensions->push_back(arr[i]); + } + return; + } + + ERR_FAIL(); +} +void EditorSceneExporter::save_scene(Node *p_node, const String &p_path, const String &p_src_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err) { + Error err = Error::FAILED; + if (get_script_instance()) { + get_script_instance()->call("_save_scene", p_node, p_path, p_src_path, p_flags, p_bake_fps); + err = Error::OK; + } + if (r_err) { + *r_err = err; + } +} + +void EditorSceneExporter::save_animation(Node *p_node, const String &p_path, const String &p_src_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err) { + Error err = Error::FAILED; + if (get_script_instance()) { + get_script_instance()->call("_save_animation", p_node, p_path, p_flags); + err = OK; + } + if (r_err) { + *r_err = err; + } +} + +void EditorSceneExporter::_bind_methods() { + BIND_VMETHOD(MethodInfo(Variant::INT, "_get_import_flags")); + BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_extensions")); + + MethodInfo mi = MethodInfo(Variant::OBJECT, "_import_scene", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "flags"), PropertyInfo(Variant::INT, "bake_fps")); + mi.return_val.class_name = "Node"; + BIND_VMETHOD(mi); + mi = MethodInfo(Variant::OBJECT, "_import_animation", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "flags"), PropertyInfo(Variant::INT, "bake_fps")); + mi.return_val.class_name = "Animation"; + BIND_VMETHOD(mi); +} + +String ResourceExporterScene::get_exporter_name() const { + + return "scene"; +} + +String ResourceExporterScene::get_visible_name() const { + + return "Scene"; +} + +void ResourceExporterScene::get_recognized_extensions(List *p_extensions) const { + for (Set >::Element *E = exporters.front(); E; E = E->next()) { + E->get()->get_exporter_extensions(p_extensions); + } +} + +String ResourceExporterScene::get_save_extension() const { + return "scn"; +} + +String ResourceExporterScene::get_resource_type() const { + + return "PackedScene"; +} + +bool ResourceExporterScene::get_option_visibility(const String &p_option, const Map &p_options) const { + + if (p_option.begins_with("animation/")) { + if (p_option != "animation/import" && !bool(p_options["animation/import"])) + return false; + + if (p_option == "animation/keep_custom_tracks" && int(p_options["animation/storage"]) == 0) + return false; + + if (p_option.begins_with("animation/optimizer/") && p_option != "animation/optimizer/enabled" && !bool(p_options["animation/optimizer/enabled"])) + return false; + + if (p_option.begins_with("animation/clip_")) { + int max_clip = p_options["animation/clips/amount"]; + int clip = p_option.get_slice("/", 1).get_slice("_", 1).to_int() - 1; + if (clip >= max_clip) + return false; + } + } + + if (p_option == "materials/keep_on_reimport" && int(p_options["materials/storage"]) == 0) { + return false; + } + + if (p_option == "meshes/lightmap_texel_size" && int(p_options["meshes/light_baking"]) < 2) { + return false; + } + + return true; +} + +int ResourceExporterScene::get_preset_count() const { + return PRESET_MAX; +} +String ResourceExporterScene::get_preset_name(int p_idx) const { + + //switch (p_idx) { + //case PRESET_SINGLE_SCENE: return TTR("Import as Single Scene"); + //case PRESET_SEPARATE_ANIMATIONS: return TTR("Import with Separate Animations"); + //case PRESET_SEPARATE_MATERIALS: return TTR("Import with Separate Materials"); + //case PRESET_SEPARATE_MESHES: return TTR("Import with Separate Objects"); + //case PRESET_SEPARATE_MESHES_AND_MATERIALS: return TTR("Import with Separate Objects+Materials"); + //case PRESET_SEPARATE_MESHES_AND_ANIMATIONS: return TTR("Import with Separate Objects+Animations"); + //case PRESET_SEPARATE_MATERIALS_AND_ANIMATIONS: return TTR("Import with Separate Materials+Animations"); + //case PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS: return TTR("Import with Separate Objects+Materials+Animations"); + //case PRESET_MULTIPLE_SCENES: return TTR("Import as Multiple Scenes"); + //case PRESET_MULTIPLE_SCENES_AND_MATERIALS: return TTR("Import as Multiple Scenes+Materials"); + //} + + return ""; +} + +void ResourceExporterScene::_create_clips(Node *scene, const Array &p_clips, bool p_bake_all) { + + if (!scene->has_node(String("AnimationPlayer"))) + return; + + Node *n = scene->get_node(String("AnimationPlayer")); + ERR_FAIL_COND(!n); + AnimationPlayer *anim = Object::cast_to(n); + ERR_FAIL_COND(!anim); + + if (!anim->has_animation("default")) + return; + + Ref default_anim = anim->get_animation("default"); + + for (int i = 0; i < p_clips.size(); i += 4) { + + String name = p_clips[i]; + float from = p_clips[i + 1]; + float to = p_clips[i + 2]; + bool loop = p_clips[i + 3]; + if (from >= to) + continue; + + Ref new_anim = memnew(Animation); + + for (int j = 0; j < default_anim->get_track_count(); j++) { + + List keys; + int kc = default_anim->track_get_key_count(j); + int dtrack = -1; + for (int k = 0; k < kc; k++) { + + float kt = default_anim->track_get_key_time(j, k); + if (kt >= from && kt < to) { + + //found a key within range, so create track + if (dtrack == -1) { + new_anim->add_track(default_anim->track_get_type(j)); + dtrack = new_anim->get_track_count() - 1; + new_anim->track_set_path(dtrack, default_anim->track_get_path(j)); + + if (kt > (from + 0.01) && k > 0) { + + if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM) { + Quat q; + Vector3 p; + Vector3 s; + default_anim->transform_track_interpolate(j, from, &p, &q, &s); + new_anim->transform_track_insert_key(dtrack, 0, p, q, s); + } + if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + Variant var = default_anim->value_track_interpolate(j, from); + new_anim->track_insert_key(dtrack, 0, var); + } + } + } + + if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM) { + Quat q; + Vector3 p; + Vector3 s; + default_anim->transform_track_get_key(j, k, &p, &q, &s); + new_anim->transform_track_insert_key(dtrack, kt - from, p, q, s); + } + if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + Variant var = default_anim->track_get_key_value(j, k); + new_anim->track_insert_key(dtrack, kt - from, var); + } + } + + if (dtrack != -1 && kt >= to) { + + if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM) { + Quat q; + Vector3 p; + Vector3 s; + default_anim->transform_track_interpolate(j, to, &p, &q, &s); + new_anim->transform_track_insert_key(dtrack, to - from, p, q, s); + } + if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + Variant var = default_anim->value_track_interpolate(j, to); + new_anim->track_insert_key(dtrack, to - from, var); + } + } + } + + if (dtrack == -1 && p_bake_all) { + new_anim->add_track(default_anim->track_get_type(j)); + dtrack = new_anim->get_track_count() - 1; + new_anim->track_set_path(dtrack, default_anim->track_get_path(j)); + if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM) { + + Quat q; + Vector3 p; + Vector3 s; + default_anim->transform_track_interpolate(j, from, &p, &q, &s); + new_anim->transform_track_insert_key(dtrack, 0, p, q, s); + default_anim->transform_track_interpolate(j, to, &p, &q, &s); + new_anim->transform_track_insert_key(dtrack, to - from, p, q, s); + } + if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + Variant var = default_anim->value_track_interpolate(j, from); + new_anim->track_insert_key(dtrack, 0, var); + Variant to_var = default_anim->value_track_interpolate(j, to); + new_anim->track_insert_key(dtrack, to - from, to_var); + } + } + } + + new_anim->set_loop(loop); + new_anim->set_length(to - from); + anim->add_animation(name, new_anim); + } + + anim->remove_animation("default"); //remove default (no longer needed) +} + +void ResourceExporterScene::_filter_anim_tracks(Ref anim, Set &keep) { + + Ref a = anim; + ERR_FAIL_COND(!a.is_valid()); + + for (int j = 0; j < a->get_track_count(); j++) { + + String path = a->track_get_path(j); + + if (!keep.has(path)) { + a->remove_track(j); + j--; + } + } +} + +void ResourceExporterScene::_filter_tracks(Node *scene, const String &p_text) { + + if (!scene->has_node(String("AnimationPlayer"))) + return; + Node *n = scene->get_node(String("AnimationPlayer")); + ERR_FAIL_COND(!n); + AnimationPlayer *anim = Object::cast_to(n); + ERR_FAIL_COND(!anim); + + Vector strings = p_text.split("\n"); + for (int i = 0; i < strings.size(); i++) { + + strings.write[i] = strings[i].strip_edges(); + } + + List anim_names; + anim->get_animation_list(&anim_names); + for (List::Element *E = anim_names.front(); E; E = E->next()) { + + String name = E->get(); + bool valid_for_this = false; + bool valid = false; + + Set keep; + Set keep_local; + + for (int i = 0; i < strings.size(); i++) { + + if (strings[i].begins_with("@")) { + + valid_for_this = false; + for (Set::Element *F = keep_local.front(); F; F = F->next()) { + keep.insert(F->get()); + } + keep_local.clear(); + + Vector filters = strings[i].substr(1, strings[i].length()).split(","); + for (int j = 0; j < filters.size(); j++) { + + String fname = filters[j].strip_edges(); + if (fname == "") + continue; + int fc = fname[0]; + bool plus; + if (fc == '+') + plus = true; + else if (fc == '-') + plus = false; + else + continue; + + String filter = fname.substr(1, fname.length()).strip_edges(); + + if (!name.matchn(filter)) + continue; + valid_for_this = plus; + } + + if (valid_for_this) + valid = true; + + } else if (valid_for_this) { + + Ref a = anim->get_animation(name); + if (!a.is_valid()) + continue; + + for (int j = 0; j < a->get_track_count(); j++) { + + String path = a->track_get_path(j); + + String tname = strings[i]; + if (tname == "") + continue; + int fc = tname[0]; + bool plus; + if (fc == '+') + plus = true; + else if (fc == '-') + plus = false; + else + continue; + + String filter = tname.substr(1, tname.length()).strip_edges(); + + if (!path.matchn(filter)) + continue; + + if (plus) + keep_local.insert(path); + else if (!keep.has(path)) { + keep_local.erase(path); + } + } + } + } + + if (valid) { + for (Set::Element *F = keep_local.front(); F; F = F->next()) { + keep.insert(F->get()); + } + _filter_anim_tracks(anim->get_animation(name), keep); + } + } +} + +void ResourceExporterScene::_optimize_animations(Node *scene, float p_max_lin_error, float p_max_ang_error, float p_max_angle) { + + if (!scene->has_node(String("AnimationPlayer"))) + return; + Node *n = scene->get_node(String("AnimationPlayer")); + ERR_FAIL_COND(!n); + AnimationPlayer *anim = Object::cast_to(n); + ERR_FAIL_COND(!anim); + + List anim_names; + anim->get_animation_list(&anim_names); + for (List::Element *E = anim_names.front(); E; E = E->next()) { + + Ref a = anim->get_animation(E->get()); + a->optimize(p_max_lin_error, p_max_ang_error, Math::deg2rad(p_max_angle)); + } +} + +static String _make_extname(const String &p_str) { + + String ext_name = p_str.replace(".", "_"); + ext_name = ext_name.replace(":", "_"); + ext_name = ext_name.replace("\"", "_"); + ext_name = ext_name.replace("<", "_"); + ext_name = ext_name.replace(">", "_"); + ext_name = ext_name.replace("/", "_"); + ext_name = ext_name.replace("|", "_"); + ext_name = ext_name.replace("\\", "_"); + ext_name = ext_name.replace("?", "_"); + ext_name = ext_name.replace("*", "_"); + + return ext_name; +} + +void ResourceExporterScene::_find_meshes(Node *p_node, Map, Transform> &meshes) { + + List pi; + p_node->get_property_list(&pi); + + MeshInstance *mi = Object::cast_to(p_node); + + if (mi) { + + Ref mesh = mi->get_mesh(); + + if (mesh.is_valid() && !meshes.has(mesh)) { + Spatial *s = mi; + Transform transform; + while (s) { + transform = transform * s->get_transform(); + s = s->get_parent_spatial(); + } + + meshes[mesh] = transform; + } + } + for (int i = 0; i < p_node->get_child_count(); i++) { + + _find_meshes(p_node->get_child(i), meshes); + } +} + +void ResourceExporterScene::_make_external_resources(Node *p_node, const String &p_base_path, bool p_make_animations, bool p_animations_as_text, bool p_keep_animations, bool p_make_materials, bool p_materials_as_text, bool p_keep_materials, bool p_make_meshes, bool p_meshes_as_text, Map, Ref > &p_animations, Map, Ref > &p_materials, Map, Ref > &p_meshes) { + + List pi; + + if (p_make_animations) { + if (Object::cast_to(p_node)) { + AnimationPlayer *ap = Object::cast_to(p_node); + + List anims; + ap->get_animation_list(&anims); + for (List::Element *E = anims.front(); E; E = E->next()) { + + Ref anim = ap->get_animation(E->get()); + ERR_CONTINUE(anim.is_null()); + + if (!p_animations.has(anim)) { + + //mark what comes from the file first, this helps eventually keep user data + for (int i = 0; i < anim->get_track_count(); i++) { + anim->track_set_imported(i, true); + } + + String ext_name; + + if (p_animations_as_text) { + ext_name = p_base_path.plus_file(_make_extname(E->get()) + ".tres"); + } else { + ext_name = p_base_path.plus_file(_make_extname(E->get()) + ".anim"); + } + + if (FileAccess::exists(ext_name) && p_keep_animations) { + //try to keep custom animation tracks + Ref old_anim = ResourceLoader::load(ext_name, "Animation", true); + if (old_anim.is_valid()) { + //meergeee + for (int i = 0; i < old_anim->get_track_count(); i++) { + if (!old_anim->track_is_imported(i)) { + old_anim->copy_track(i, anim); + } + } + anim->set_loop(old_anim->has_loop()); + } + } + + anim->set_path(ext_name, true); //if not set, then its never saved externally + ResourceSaver::save(ext_name, anim, ResourceSaver::FLAG_CHANGE_PATH); + p_animations[anim] = anim; + } + } + } + } + + p_node->get_property_list(&pi); + + for (List::Element *E = pi.front(); E; E = E->next()) { + + if (E->get().type == Variant::OBJECT) { + + Ref mat = p_node->get(E->get().name); + + if (p_make_materials && mat.is_valid() && mat->get_name() != "") { + + if (!p_materials.has(mat)) { + + String ext_name; + + if (p_materials_as_text) { + ext_name = p_base_path.plus_file(_make_extname(mat->get_name()) + ".tres"); + } else { + ext_name = p_base_path.plus_file(_make_extname(mat->get_name()) + ".material"); + } + + if (p_keep_materials && FileAccess::exists(ext_name)) { + //if exists, use it + p_materials[mat] = ResourceLoader::load(ext_name); + } else { + + ResourceSaver::save(ext_name, mat, ResourceSaver::FLAG_CHANGE_PATH); + p_materials[mat] = ResourceLoader::load(ext_name, "", true); // disable loading from the cache. + } + } + + if (p_materials[mat] != mat) { + + p_node->set(E->get().name, p_materials[mat]); + } + } else { + + Ref mesh = p_node->get(E->get().name); + + if (mesh.is_valid()) { + + bool mesh_just_added = false; + + if (p_make_meshes) { + + if (!p_meshes.has(mesh)) { + + //meshes are always overwritten, keeping them is not practical + String ext_name; + + if (p_meshes_as_text) { + ext_name = p_base_path.plus_file(_make_extname(mesh->get_name()) + ".tres"); + } else { + ext_name = p_base_path.plus_file(_make_extname(mesh->get_name()) + ".mesh"); + } + + ResourceSaver::save(ext_name, mesh, ResourceSaver::FLAG_CHANGE_PATH); + p_meshes[mesh] = ResourceLoader::load(ext_name); + p_node->set(E->get().name, p_meshes[mesh]); + mesh_just_added = true; + } + } + + if (p_make_materials) { + + if (mesh_just_added || !p_meshes.has(mesh)) { + + for (int i = 0; i < mesh->get_surface_count(); i++) { + mat = mesh->surface_get_material(i); + + if (!mat.is_valid()) + continue; + if (mat->get_name() == "") + continue; + + if (!p_materials.has(mat)) { + String ext_name; + + if (p_materials_as_text) { + ext_name = p_base_path.plus_file(_make_extname(mat->get_name()) + ".tres"); + } else { + ext_name = p_base_path.plus_file(_make_extname(mat->get_name()) + ".material"); + } + + if (p_keep_materials && FileAccess::exists(ext_name)) { + //if exists, use it + p_materials[mat] = ResourceLoader::load(ext_name); + } else { + + ResourceSaver::save(ext_name, mat, ResourceSaver::FLAG_CHANGE_PATH); + p_materials[mat] = ResourceLoader::load(ext_name, "", true); // disable loading from the cache. + } + } + + if (p_materials[mat] != mat) { + + mesh->surface_set_material(i, p_materials[mat]); + + //re-save the mesh since a material is now assigned + if (p_make_meshes) { + + String ext_name; + + if (p_meshes_as_text) { + ext_name = p_base_path.plus_file(_make_extname(mesh->get_name()) + ".tres"); + } else { + ext_name = p_base_path.plus_file(_make_extname(mesh->get_name()) + ".mesh"); + } + + ResourceSaver::save(ext_name, mesh, ResourceSaver::FLAG_CHANGE_PATH); + p_meshes[mesh] = ResourceLoader::load(ext_name); + } + } + } + + if (!p_make_meshes) { + p_meshes[mesh] = Ref(); //save it anyway, so it won't be checked again + } + } + } + } + } + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + + _make_external_resources(p_node->get_child(i), p_base_path, p_make_animations, p_animations_as_text, p_keep_animations, p_make_materials, p_materials_as_text, p_keep_materials, p_make_meshes, p_meshes_as_text, p_animations, p_materials, p_meshes); + } +} + +void ResourceExporterScene::get_export_options(List *r_options, int p_preset) const { + + // r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_type", PROPERTY_HINT_TYPE_STRING, "Node"), "Spatial")); + // r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_name"), "Scene Root")); + + List script_extentions; + ResourceLoader::get_recognized_extensions_for_type("Script", &script_extentions); + + String script_ext_hint; + + for (List::Element *E = script_extentions.front(); E; E = E->next()) { + if (script_ext_hint != "") + script_ext_hint += ","; + script_ext_hint += "*." + E->get(); + } + + //bool materials_out = p_preset == PRESET_SEPARATE_MATERIALS || p_preset == PRESET_SEPARATE_MESHES_AND_MATERIALS || p_preset == PRESET_MULTIPLE_SCENES_AND_MATERIALS || p_preset == PRESET_SEPARATE_MATERIALS_AND_ANIMATIONS || p_preset == PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS; + //bool meshes_out = p_preset == PRESET_SEPARATE_MESHES || p_preset == PRESET_SEPARATE_MESHES_AND_MATERIALS || p_preset == PRESET_SEPARATE_MESHES_AND_ANIMATIONS || p_preset == PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS; + //bool scenes_out = p_preset == PRESET_MULTIPLE_SCENES || p_preset == PRESET_MULTIPLE_SCENES_AND_MATERIALS; + //bool animations_out = p_preset == PRESET_SEPARATE_ANIMATIONS || p_preset == PRESET_SEPARATE_MESHES_AND_ANIMATIONS || p_preset == PRESET_SEPARATE_MATERIALS_AND_ANIMATIONS || p_preset == PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS; + + // r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "nodes/root_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001"), 1.0)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/custom_script", PROPERTY_HINT_FILE, script_ext_hint), "")); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "nodes/storage", PROPERTY_HINT_ENUM, "Single Scene,Instanced Sub-Scenes"), scenes_out ? 1 : 0)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "materials/location", PROPERTY_HINT_ENUM, "Node,Mesh"), (meshes_out || materials_out) ? 1 : 0)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "materials/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.material),Files (.tres)", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), materials_out ? 1 : 0)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "materials/keep_on_reimport"), materials_out)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/compress"), true)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/ensure_tangents"), true)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.mesh),Files (.tres)"), meshes_out ? 1 : 0)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Enable,Gen Lightmaps", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.1)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "external_files/store_in_subdir"), false)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 15)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "animation/filter_script", PROPERTY_HINT_MULTILINE_TEXT), "")); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.anim),Files (.tres)", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), animations_out)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/keep_custom_tracks"), animations_out)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/optimizer/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/optimizer/max_linear_error"), 0.05)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/optimizer/max_angular_error"), 0.01)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/optimizer/max_angle"), 22)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/optimizer/remove_unused_tracks"), true)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/clips/amount", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); + // for (int i = 0; i < 256; i++) { + // r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "animation/clip_" + itos(i + 1) + "/name"), "")); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/clip_" + itos(i + 1) + "/start_frame"), 0)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/clip_" + itos(i + 1) + "/end_frame"), 0)); + // r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/clip_" + itos(i + 1) + "/loops"), false)); + // } +} + +void ResourceExporterScene::_replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner) { + + if (p_node != p_new_owner && p_node->get_owner() == p_scene) { + p_node->set_owner(p_new_owner); + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *n = p_node->get_child(i); + _replace_owner(n, p_scene, p_new_owner); + } +} + +Error ResourceExporterScene::export_(Node *p_node, const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata) { + const String &src_path = p_source_file; + + Ref exporter; + String ext = src_path.get_extension().to_lower(); + + EditorProgress progress("export", TTR("Export Scene"), 104); + progress.step(TTR("Exporting Scene..."), 0); + + for (Set >::Element *E = exporters.front(); E; E = E->next()) { + + List extensions; + E->get()->get_exporter_extensions(&extensions); + + for (List::Element *F = extensions.front(); F; F = F->next()) { + + if (F->get().to_lower() == ext) { + + exporter = E->get(); + break; + } + } + + if (exporter.is_valid()) + break; + } + + ERR_FAIL_COND_V(!exporter.is_valid(), ERR_FILE_UNRECOGNIZED); + + float fps = p_options["animation/fps"]; + + int import_flags = EditorSceneImporter::IMPORT_ANIMATION_DETECT_LOOP; + if (!bool(p_options["animation/optimizer/remove_unused_tracks"])) + import_flags |= EditorSceneImporter::IMPORT_ANIMATION_FORCE_ALL_TRACKS_IN_ALL_CLIPS; + + if (bool(p_options["animation/import"])) + import_flags |= EditorSceneImporter::IMPORT_ANIMATION; + + if (int(p_options["meshes/compress"])) + import_flags |= EditorSceneImporter::IMPORT_USE_COMPRESSION; + + if (bool(p_options["meshes/ensure_tangents"])) + import_flags |= EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS; + + if (int(p_options["materials/location"]) == 0) + import_flags |= EditorSceneImporter::IMPORT_MATERIALS_IN_INSTANCES; + + Error err = OK; + exporter->save_scene(p_node, p_save_path, src_path, import_flags, fps, r_gen_files, &err); + if (err != OK) { + return err; + } + + progress.step(TTR("Saving..."), 104); + + return OK; +} + +ResourceExporterScene *ResourceExporterScene::singleton = NULL; + +ResourceExporterScene::ResourceExporterScene() { + singleton = this; +} diff --git a/editor/saver/resource_saver_scene.h b/editor/saver/resource_saver_scene.h new file mode 100644 index 000000000000..da3fa12836b1 --- /dev/null +++ b/editor/saver/resource_saver_scene.h @@ -0,0 +1,107 @@ +/*************************************************************************/ +/* resource_export_scene.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 ResourceSaverScene_H +#define ResourceSaverScene_H + +#include "core/io/resource_exporter.h" +#include "scene/resources/animation.h" +#include "scene/resources/mesh.h" +#include "scene/resources/shape.h" + +class Material; + +class EditorSceneExporter : public Reference { + + GDCLASS(EditorSceneExporter, Reference); + +protected: + static void _bind_methods(); + +public: + enum Exportflags { + }; + virtual uint32_t get_save_flags() const; + virtual void get_exporter_extensions(List *r_extensions) const; + virtual void save_scene(Node *p_node, const String &p_path, const String &p_src_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err = NULL); + virtual void save_animation(Node *p_node, const String &p_path, const String &p_src_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err = NULL); + + EditorSceneExporter() {} +}; + +class ResourceExporterScene : public ResourceExporter { + GDCLASS(ResourceExporterScene, ResourceExporter); + + Set > exporters; + + static ResourceExporterScene *singleton; + + enum Presets { + PRESET_MAX + }; + + void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner); + +public: + static ResourceExporterScene *get_singleton() { return singleton; } + + const Set > &get_savers() const { return exporters; } + + void add_exporter(Ref p_exporter) { exporters.insert(p_exporter); } + void remove_exporter(Ref p_exporter) { exporters.erase(p_exporter); } + + virtual String get_exporter_name() const; + virtual String get_visible_name() const; + virtual void get_recognized_extensions(List *p_extensions) const; + virtual String get_save_extension() const; + virtual String get_resource_type() const; + + virtual int get_preset_count() const; + virtual String get_preset_name(int p_idx) const; + + virtual void get_export_options(List *r_options, int p_preset = 0) const; + virtual bool get_option_visibility(const String &p_option, const Map &p_options) const; + virtual int get_export_order() const { return 100; } //after everything + + void _find_meshes(Node *p_node, Map, Transform> &meshes); + + void _make_external_resources(Node *p_node, const String &p_base_path, bool p_make_animations, bool p_animations_as_text, bool p_keep_animations, bool p_make_materials, bool p_materials_as_text, bool p_keep_materials, bool p_make_meshes, bool p_meshes_as_text, Map, Ref > &p_animations, Map, Ref > &p_materials, Map, Ref > &p_meshes); + + void _create_clips(Node *scene, const Array &p_clips, bool p_bake_all); + void _filter_anim_tracks(Ref anim, Set &keep); + void _filter_tracks(Node *scene, const String &p_text); + void _optimize_animations(Node *scene, float p_max_lin_error, float p_max_ang_error, float p_max_angle); + virtual Error export_(Node *p_node, const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files = NULL, Variant *r_metadata = NULL); + + ResourceExporterScene(); + ~ResourceExporterScene() {} +}; + +#endif // ResourceSaverScene_H diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 9409b33f2456..9bdb40928673 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -553,6 +553,11 @@ void CSGShape::_notification(int p_what) { } } +Ref CSGShape::get_calculated_mesh() { + _update_shape(); + return root_mesh; +} + void CSGShape::set_operation(Operation p_operation) { operation = p_operation; @@ -597,6 +602,7 @@ Array CSGShape::get_meshes() const { } void CSGShape::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_calculated_mesh"), &CSGShape::get_calculated_mesh); ClassDB::bind_method(D_METHOD("_update_shape"), &CSGShape::_update_shape); ClassDB::bind_method(D_METHOD("is_root_shape"), &CSGShape::is_root_shape); diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index 6c9419b3c2b5..8639ad1e7a89 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -119,6 +119,8 @@ class CSGShape : public GeometryInstance { public: Array get_meshes() const; + Ref get_calculated_mesh(); + void set_operation(Operation p_operation); Operation get_operation() const; @@ -201,6 +203,7 @@ class CSGMesh : public CSGPrimitive { void set_material(const Ref &p_material); Ref get_material() const; + Ref get_calculated_mesh(); }; class CSGSphere : public CSGPrimitive { diff --git a/modules/gltf/SCsub b/modules/gltf/SCsub new file mode 100644 index 000000000000..176d4f4d11da --- /dev/null +++ b/modules/gltf/SCsub @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_gltf = env_modules.Clone() +env_gltf.Prepend(CPPPATH=['.']) + +# Godot's own source files +env_gltf.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/gltf/config.py b/modules/gltf/config.py new file mode 100644 index 000000000000..5ea30753f9ac --- /dev/null +++ b/modules/gltf/config.py @@ -0,0 +1,4 @@ +def can_build(env, platform): + return True +def configure(env): + pass diff --git a/modules/gltf/editor_scene_exporter_gltf.cpp b/modules/gltf/editor_scene_exporter_gltf.cpp new file mode 100644 index 000000000000..cd966cc6bbcb --- /dev/null +++ b/modules/gltf/editor_scene_exporter_gltf.cpp @@ -0,0 +1,150 @@ +/*************************************************************************/ +/* editor_scene_saver_gltf.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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.h" +#include "core/object.h" +#include "core/project_settings.h" +#include "core/vector.h" +#include "editor/saver/resource_saver_scene.h" +#include "gltf_document.h" +#include "modules/csg/csg_shape.h" +#include "modules/gridmap/grid_map.h" +#include "scene/3d/mesh_instance.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" + +void EditorSceneExporterGLTF::get_exporter_extensions(List *r_extensions) const { + r_extensions->push_back("*.gltf"); + r_extensions->push_back("*.glb"); +} + +void EditorSceneExporterGLTF::save_scene(Node *p_node, const String &p_path, const String &p_src_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err) { + Error err = Error::FAILED; + Vector csg_items; + _find_all_csg_roots(csg_items, p_node, p_node); + + Vector grid_map_items; + _find_all_gridmaps(grid_map_items, p_node, p_node); + + Vector meshes; + for (int32_t i = 0; i < csg_items.size(); i++) { + Ref mesh = csg_items[i]->get_calculated_mesh(); + MeshInfo mesh_info; + for (int32_t material_i = 0; material_i < mesh->get_surface_count(); material_i++) { + mesh_info.materials.push_back(mesh->surface_get_material(material_i)); + } + mesh_info.mesh = mesh; + mesh_info.transform = csg_items[i]->get_transform(); + mesh_info.name = csg_items[i]->get_name(); + mesh_info.original_node = csg_items[i]; + meshes.push_back(mesh_info); + } + for (int32_t i = 0; i < grid_map_items.size(); i++) { + Array cells = grid_map_items[i]->get_used_cells(); + for (int32_t k = 0; k < cells.size(); k++) { + Vector3 cell_location = cells[k]; + int32_t cell = grid_map_items[i]->get_cell_item(cell_location.x, cell_location.y, cell_location.z); + MeshInfo mesh_info; + Ref mesh = grid_map_items[i]->get_mesh_library()->get_item_mesh(cell); + for (int32_t material_i = 0; material_i < mesh->get_surface_count(); material_i++) { + mesh_info.materials.push_back(mesh->surface_get_material(material_i)); + } + mesh_info.mesh = mesh; + Transform cell_xform; + cell_xform.basis.set_orthogonal_index(grid_map_items[i]->get_cell_item_orientation(cell_location.x, cell_location.y, cell_location.z)); + cell_xform.basis.scale(Vector3(grid_map_items[i]->get_cell_scale(), grid_map_items[i]->get_cell_scale(), grid_map_items[i]->get_cell_scale())); + cell_xform.set_origin(grid_map_items[i]->map_to_world(cell_location.x, cell_location.y, cell_location.z)); + mesh_info.transform = cell_xform * grid_map_items[i]->get_transform(); + mesh_info.name = grid_map_items[i]->get_mesh_library()->get_item_name(cell); + meshes.push_back(mesh_info); + } + } + + for (int32_t i = 0; i < meshes.size(); i++) { + MeshInstance *mi = memnew(MeshInstance); + mi->set_mesh(meshes[i].mesh); + for (int32_t j = 0; j < meshes[i].materials.size(); j++) { + mi->set_surface_material(j, meshes[i].materials[j]); + } + mi->set_name(meshes[i].name); + mi->set_transform(meshes[i].transform); + if (meshes[i].original_node) { + meshes[i].original_node->replace_by(mi); + } else { + p_node->add_child(mi); + mi->set_owner(p_node); + } + } + + Ref gltf_document; + gltf_document.instance(); + + GLTFDocument::GLTFState state; + const GLTFDocument::GLTFNodeIndex scene_root = 0; + gltf_document->_convert_scene_node(state, p_node, p_node, scene_root, -1); + gltf_document->_convert_mesh_instances(state); + gltf_document->_convert_skeletons(state); + state.scene_name = p_node->get_name(); + + if (p_path.to_lower().ends_with("glb")) { + //binary file + //text file + //err = _serialize_glb(p_path, *state); + } else { + //text file + err = gltf_document->_serialize_json(p_path, state); + } + if (r_err) { + *r_err = err; + } +} + +void EditorSceneExporterGLTF::_find_all_gridmaps(Vector &r_items, Node *p_current_node, const Node *p_owner) { + GridMap *gridmap = Object::cast_to(p_current_node); + if (gridmap != NULL) { + r_items.push_back(gridmap); + } + for (int32_t i = 0; i < p_current_node->get_child_count(); i++) { + _find_all_gridmaps(r_items, p_current_node->get_child(i), p_owner); + } +} + +void EditorSceneExporterGLTF::_find_all_csg_roots(Vector &r_items, Node *p_current_node, const Node *p_owner) { + CSGShape *csg = Object::cast_to(p_current_node); + if (csg != NULL && csg->is_root_shape()) { + r_items.push_back(csg); + } + for (int32_t i = 0; i < p_current_node->get_child_count(); i++) { + _find_all_csg_roots(r_items, p_current_node->get_child(i), p_owner); + } +} diff --git a/modules/gltf/editor_scene_exporter_gltf.h b/modules/gltf/editor_scene_exporter_gltf.h new file mode 100644 index 000000000000..b0e0a51aeee6 --- /dev/null +++ b/modules/gltf/editor_scene_exporter_gltf.h @@ -0,0 +1,69 @@ +/*************************************************************************/ +/* editor_scene_exporter_gltf.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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/object.h" +#include "core/project_settings.h" +#include "core/vector.h" +#include "editor/editor_plugin.h" +#include "editor/saver/editor_saver_plugin.h" +#include "editor/saver/resource_saver_scene.h" +#include "modules/csg/csg_shape.h" +#include "modules/gridmap/grid_map.h" +#include "scene/3d/mesh_instance.h" +#include "scene/gui/check_box.h" +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" +#include "scene/resources/surface_tool.h" + +#ifndef EDITOR_SCENE_EXPORTER_GLTF_H +#define EDITOR_SCENE_EXPORTER_GLTF_H +class EditorSceneExporter; +class EditorSceneExporterGLTF : public EditorSceneExporter { + + GDCLASS(EditorSceneExporterGLTF, EditorSceneExporter); + +public: + virtual void save_scene(Node *p_node, const String &p_path, const String &p_src_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err = NULL); + + struct MeshInfo { + Transform transform; + Ref mesh; + String name; + Vector > materials; + Node *original_node = NULL; + }; + void _find_all_gridmaps(Vector &r_items, Node *p_current_node, const Node *p_owner); + void _find_all_csg_roots(Vector &r_items, Node *p_current_node, const Node *p_owner); + void get_exporter_extensions(List *r_extensions) const; + EditorSceneExporterGLTF() {} + ~EditorSceneExporterGLTF() {} +}; + +#endif diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp new file mode 100644 index 000000000000..ac889c43dee6 --- /dev/null +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp @@ -0,0 +1,109 @@ +/*************************************************************************/ +/* editor_scene_exporter_gltf_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 "editor/saver/resource_saver_scene.h" +#include "editor_scene_exporter_gltf.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"; +} + +void SceneExporterGLTFPlugin::_bind_methods() { + ClassDB::bind_method("_gltf2_dialog_action", &SceneExporterGLTFPlugin::_gltf2_dialog_action); + ClassDB::bind_method(D_METHOD("convert_scene_to_gltf2"), &SceneExporterGLTFPlugin::convert_scene_to_gltf2); +} + +void SceneExporterGLTFPlugin::_notification(int notification) { + if (notification == NOTIFICATION_ENTER_TREE) { + editor->add_tool_menu_item("Convert Scene to GLTF", this, "convert_scene_to_gltf2"); + } else if (notification == NOTIFICATION_EXIT_TREE) { + editor->remove_tool_menu_item("Convert Scene to GLTF"); + } +} + +bool SceneExporterGLTFPlugin::has_main_screen() const { + return false; +} + +SceneExporterGLTFPlugin::SceneExporterGLTFPlugin(EditorNode *p_node) { + editor = p_node; + convert_gltf2.instance(); + file_export_lib_merge = NULL; + file_export_lib = NULL; +} + +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; + } + if (FileAccess::exists(p_file) && file_export_lib_merge->is_pressed()) { + Ref scene = ResourceLoader::load(p_file, "PackedScene"); + if (scene.is_null()) { + editor->show_accept(TTR("Can't load scene for merging!"), TTR("OK")); + return; + } else { + root->add_child(scene->instance()); + } + } + List deps; + convert_gltf2->save_scene(root, p_file, p_file, 0, 1000.0f, &deps); + EditorFileSystem::get_singleton()->scan_changes(); + file_export_lib->queue_delete(); + file_export_lib_merge->queue_delete(); +} + +void SceneExporterGLTFPlugin::convert_scene_to_gltf2(Variant p_user_data) { + file_export_lib = memnew(EditorFileDialog); + file_export_lib->set_title(TTR("Export Library")); + file_export_lib->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_export_lib_merge = memnew(CheckBox); + file_export_lib_merge->set_text(TTR("Merge With Existing")); + file_export_lib_merge->set_pressed(false); + file_export_lib->get_vbox()->add_child(file_export_lib_merge); + editor->get_gui_base()->add_child(file_export_lib); + file_export_lib->clear_filters(); + file_export_lib->add_filter("*.glb"); + file_export_lib->add_filter("*.gltf"); + file_export_lib->popup_centered_ratio(); + file_export_lib->set_title(TTR("Export Mesh GLTF2")); + file_export_lib->connect("file_selected", this, "_gltf2_dialog_action"); +} diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor_scene_exporter_gltf_plugin.h new file mode 100644 index 000000000000..c5e36356821f --- /dev/null +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* editor_scene_exporter_gltf_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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_scene_exporter_gltf.h" + +class SceneExporterGLTFPlugin : public EditorPlugin { + + GDCLASS(SceneExporterGLTFPlugin, EditorPlugin); + + Ref convert_gltf2; + EditorNode *editor; + CheckBox *file_export_lib_merge; + EditorFileDialog *file_export_lib; + +protected: + static void _bind_methods(); + +public: + void _gltf_dialog_action(String p_file); + void convert_scene_to_gltf(Variant p_user_data); + virtual String get_name() const; + virtual void _notification(int notification); + bool has_main_screen() const; + + SceneExporterGLTFPlugin(class EditorNode *p_node); + void _gltf2_dialog_action(String p_file); + void convert_scene_to_gltf2(Variant p_user_data); +}; + +#endif diff --git a/modules/gltf/editor_scene_importer_gltf.cpp b/modules/gltf/editor_scene_importer_gltf.cpp new file mode 100644 index 000000000000..8a1a22430f61 --- /dev/null +++ b/modules/gltf/editor_scene_importer_gltf.cpp @@ -0,0 +1,102 @@ +/*************************************************************************/ +/* editor_scene_importer_gltf.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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/resources/packed_scene.h" +#include "scene/resources/surface_tool.h" + +uint32_t EditorSceneImporterGLTF::get_import_flags() const { + + return IMPORT_SCENE | IMPORT_ANIMATION; +} +void EditorSceneImporterGLTF::get_extensions(List *r_extensions) const { + + r_extensions->push_back("gltf"); + r_extensions->push_back("glb"); +} + +Spatial *EditorSceneImporterGLTF::_generate_scene(GLTFDocument::GLTFState &state, const int p_bake_fps) { + + Spatial *root = memnew(Spatial); + + // scene_name is already unique + root->set_name(state.scene_name); + + for (int i = 0; i < state.root_nodes.size(); ++i) { + gltf_document->_generate_scene_node(state, root, root, state.root_nodes[i]); + } + + gltf_document->_process_mesh_instances(state, root); + + if (state.animations.size()) { + AnimationPlayer *ap = memnew(AnimationPlayer); + ap->set_name("AnimationPlayer"); + root->get_child(0)->add_child(ap); + ap->set_owner(root); + + for (int i = 0; i < state.animations.size(); i++) { + gltf_document->_import_animation(state, ap, i, p_bake_fps); + } + } + Map reown; + Node *base = root->get_child(0); + reown[root] = base; + return Object::cast_to(base->duplicate_and_reown(reown)); +} + +Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err) { + + GLTFDocument::GLTFState state; + ERR_FAIL_COND_V(gltf_document->parse(&state, p_path) != Error::OK, NULL); + + Spatial *scene = _generate_scene(state, p_bake_fps); + + return scene; +} + +Ref EditorSceneImporterGLTF::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { + + return Ref(); +} + +EditorSceneImporterGLTF::EditorSceneImporterGLTF() { + gltf_document.instance(); +} diff --git a/modules/gltf/editor_scene_importer_gltf.h b/modules/gltf/editor_scene_importer_gltf.h new file mode 100644 index 000000000000..597b7f4c6112 --- /dev/null +++ b/modules/gltf/editor_scene_importer_gltf.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* editor_scene_importer_gltf.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 "gltf_document.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/spatial.h" + +class AnimationPlayer; +class BoneAttachment; +class MeshInstance; + +class EditorSceneImporterGLTF : public EditorSceneImporter { + + GDCLASS(EditorSceneImporterGLTF, EditorSceneImporter); + + Ref gltf_document; + Spatial *_generate_scene(GLTFDocument::GLTFState &state, const int p_bake_fps); + +public: + virtual uint32_t get_import_flags() const; + virtual void get_extensions(List *r_extensions) const; + virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps = NULL, Error *r_err = NULL); + virtual Ref import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps); + + EditorSceneImporterGLTF(); +}; + +#endif // EDITOR_SCENE_IMPORTER_GLTF_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp new file mode 100644 index 000000000000..2e09bd80a21e --- /dev/null +++ b/modules/gltf/gltf_document.cpp @@ -0,0 +1,5336 @@ +/*************************************************************************/ +/* gltf_document.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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/crypto/crypto_core.h" +#include "core/io/json.h" +#include "core/math/disjoint_set.h" +#include "drivers/png/png_driver_common.h" +#include "editor/import/resource_importer_scene.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/3d/skeleton.h" +#include "scene/3d/spatial.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/surface_tool.h" +#include + +Error GLTFDocument::_serialize_json(const String &p_path, GLTFState &state) { + + if (!state.buffers.size()) { + state.buffers.push_back(Vector()); + } + + /* STEP 1 SERIALIZE CAMERAS */ + Error err = _serialize_cameras(state); + if (err != OK) { + return Error::FAILED; + } + + // /* STEP 2 CREATE SKINS */ + err = _serialize_skins(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 3 SERIALIZE MESHES (we have enough info now) */ + err = _serialize_meshes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 4 SERIALIZE TEXTURES */ + err = _serialize_materials(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 5 SERIALIZE IMAGES */ + err = _serialize_images(state, p_path); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 6 SERIALIZE TEXTURES */ + err = _serialize_textures(state); + if (err != OK) { + return Error::FAILED; + } + + // /* STEP 7 SERIALIZE ANIMATIONS */ + err = _serialize_animations(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 8 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 9 SERIALIZE BUFFER VIEWS */ + err = _encode_buffer_views(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 10 SERIALIZE BUFFERS */ + err = _encode_buffers(state, p_path); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 11 SERIALIZE NODES */ + err = _serialize_nodes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 12 SERIALIZE SCENE */ + err = _serialize_scenes(state); + if (err != OK) { + return Error::FAILED; + } + + 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; + state.json["asset"] = asset; + ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED); + ERR_FAIL_COND_V(!state.json.has("asset"), Error::FAILED); + + FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE, &err); + if (!f) { + return err; + } + + PoolByteArray array; + String json = JSON::print(state.json); + f->store_string(json); + return OK; +} + +Error GLTFDocument::_serialize_scenes(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, GLTFState &state) { + + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (!f) { + return err; + } + + Vector array; + array.resize(f->get_len()); + f->get_buffer(array.ptrw(), array.size()); + String text; + text.parse_utf8((const char *)array.ptr(), array.size()); + + String err_txt; + int err_line; + Variant v; + err = JSON::parse(text, v, err_txt, err_line); + if (err != OK) { + _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); + return err; + } + state.json = v; + + return OK; +} + +Error GLTFDocument::_parse_glb(const String &p_path, GLTFState &state) { + + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (!f) { + return err; + } + + uint32_t magic = f->get_32(); + ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF + f->get_32(); // version + f->get_32(); // length + + uint32_t chunk_length = f->get_32(); + uint32_t chunk_type = f->get_32(); + + ERR_FAIL_COND_V(chunk_type != 0x4E4F534A, ERR_PARSE_ERROR); //JSON + Vector json_data; + json_data.resize(chunk_length); + uint32_t len = f->get_buffer(json_data.ptrw(), chunk_length); + ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); + + String text; + text.parse_utf8((const char *)json_data.ptr(), json_data.size()); + + String err_txt; + int err_line; + Variant v; + err = JSON::parse(text, v, err_txt, err_line); + if (err != OK) { + _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); + return err; + } + + state.json = v; + + //data? + + chunk_length = f->get_32(); + chunk_type = f->get_32(); + + if (f->eof_reached()) { + return OK; //all good + } + + ERR_FAIL_COND_V(chunk_type != 0x004E4942, ERR_PARSE_ERROR); //BIN + + state.glb_data.resize(chunk_length); + len = f->get_buffer(state.glb_data.ptrw(), chunk_length); + ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); + + return OK; +} + +static 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 PoolRealArray _xform_to_array(const Transform p_transform) { + PoolRealArray 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(GLTFState &state) { + Array nodes; + for (int i = 0; i < state.nodes.size(); i++) { + Dictionary node; + GLTFNode *n = state.nodes[i]; + if (!n->name.empty()) { + node["name"] = n->name; + } + if (n->camera != -1) { + node["camera"] = n->camera; + } + if (n->mesh != -1) { + node["mesh"] = n->mesh; + } + if (n->skin != -1) { + node["skin"] = n->skin; + } + 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(const String &name) { + RegEx regex("([^a-zA-Z0-9_ -]+)"); + String p_name = regex.sub(name, "", true); + return p_name; +} + +String GLTFDocument::_gen_unique_name(GLTFState &state, const String &p_name) { + + const String s_name = _sanitize_scene_name(p_name); + + String name; + int index = 1; + while (true) { + name = s_name; + + if (index > 1) { + name += " " + itos(index); + } + if (!state.unique_names.has(name)) { + break; + } + index++; + } + + state.unique_names.insert(name); + + return name; +} + +String GLTFDocument::_sanitize_bone_name(const String &name) { + String p_name = name.camelcase_to_underscore(true); + + RegEx pattern_del("([^a-zA-Z0-9_ ])+"); + p_name = pattern_del.sub(p_name, "", true); + + RegEx pattern_nospace(" +"); + p_name = pattern_nospace.sub(p_name, "_", true); + + RegEx pattern_multiple("_+"); + p_name = pattern_multiple.sub(p_name, "_", true); + + RegEx pattern_padded("0+(\\d+)"); + p_name = pattern_padded.sub(p_name, "$1", true); + + return p_name; +} + +String GLTFDocument::_gen_unique_bone_name(GLTFState &state, const GLTFDocument::GLTFSkeletonIndex skel_i, const String &p_name) { + + const String s_name = _sanitize_bone_name(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(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 GLTFDocument::_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("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 (GLTFDocument::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 GLTFDocument::_compute_node_heights(GLTFState &state) { + + state.root_nodes.clear(); + for (GLTFDocument::GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { + GLTFNode *node = state.nodes[node_i]; + node->height = 0; + + GLTFDocument::GLTFNodeIndex current_i = node_i; + while (current_i >= 0) { + const GLTFDocument::GLTFNodeIndex parent_i = state.nodes[current_i]->parent; + if (parent_i >= 0) { + ++node->height; + } + current_i = parent_i; + } + + if (node->height == 0) { + state.root_nodes.push_back(node_i); + } + } +} + +static Vector _parse_base64_uri(const String &uri) { + + int start = uri.find(","); + ERR_FAIL_COND_V(start == -1, Vector()); + + CharString substr = uri.right(start + 1).ascii(); + + int strlen = substr.length(); + + Vector buf; + buf.resize(strlen / 4 * 3 + 1 + 1); + + size_t len = 0; + ERR_FAIL_COND_V(CryptoCore::b64_decode(buf.ptrw(), buf.size(), &len, (unsigned char *)substr.get_data(), strlen) != OK, Vector()); + + buf.resize(len); + + return buf; +} + +Error GLTFDocument::_encode_buffers(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++) { + // if (i == 0 && state.glb_data.size()) { + // state.buffers.push_back(state.glb_data); + + // } else { + // } + Dictionary gltf_buffer; + + Vector buffer_data = state.buffers[i]; + + // if (uri.findn("data:application/octet-stream;base64") == 0) { + // //embedded data + // buffer_data = _parse_base64_uri(uri); + // } else { + // } + 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()); + 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(GLTFState &state, const String &p_base_path) { + + if (!state.json.has("buffers")) + return OK; + + const Array &buffers = state.json["buffers"]; + for (GLTFBufferIndex i = 0; i < buffers.size(); i++) { + + if (i == 0 && state.glb_data.size()) { + state.buffers.push_back(state.glb_data); + + } else { + const Dictionary &buffer = buffers[i]; + if (buffer.has("uri")) { + + Vector buffer_data; + String uri = buffer["uri"]; + + if (uri.findn("data:application/octet-stream;base64") == 0) { + //embedded data + buffer_data = _parse_base64_uri(uri); + } else { + + uri = p_base_path.plus_file(uri).replace("\\", "/"); //fix for windows + buffer_data = FileAccess::get_file_as_array(uri); + ERR_FAIL_COND_V(buffer.size() == 0, ERR_PARSE_ERROR); + } + + 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(GLTFState &state) { + + Array buffers; + for (GLTFBufferViewIndex i = 0; i < state.buffer_views.size(); i++) { + + Dictionary d; + + 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(GLTFState &state) { + + ERR_FAIL_COND_V(!state.json.has("bufferViews"), ERR_FILE_CORRUPT); + 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 == 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(GLTFDocument::GLTFState &state) { + + Array accessors; + for (GLTFDocument::GLTFAccessorIndex i = 0; i < state.accessors.size(); i++) { + + Dictionary d; + + GLTFDocument::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["max"] = accessor.max; + d["min"] = accessor.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(GLTFDocument::GLTFState &state) { + + ERR_FAIL_COND_V(!state.json.has("accessors"), ERR_FILE_CORRUPT); + const Array &accessors = state.json["accessors"]; + for (GLTFDocument::GLTFAccessorIndex i = 0; i < accessors.size(); i++) { + + const Dictionary &d = accessors[i]; + + GLTFDocument::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("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 GLTFDocument::_get_component_type_name(const uint32_t p_component) { + + switch (p_component) { + case GLTFDocument::COMPONENT_TYPE_BYTE: return "Byte"; + case GLTFDocument::COMPONENT_TYPE_UNSIGNED_BYTE: return "UByte"; + case GLTFDocument::COMPONENT_TYPE_SHORT: return "Short"; + case GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT: return "UShort"; + case GLTFDocument::COMPONENT_TYPE_INT: return "Int"; + case GLTFDocument::COMPONENT_TYPE_FLOAT: return "Float"; + } + + return ""; +} + +String GLTFDocument::_get_type_name(const GLTFType p_component) { + + static const char *names[] = { + "float", + "vec2", + "vec3", + "vec4", + "mat2", + "mat3", + "mat4" + }; + + return names[p_component]; +} + +Error GLTFDocument::_encode_buffer_view(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, GLTFDocument::GLTFBufferViewIndex &r_bufferview_i) { + + 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: { + } + } + + GLTFBufferView bv; + const uint32_t offset = bv.byte_offset = byte_offset; + Vector &gltf_buffer = state.buffers.write[0]; + + int stride = _get_component_type_size(component_type); + if (for_vertex && stride % 4) { + stride += 4 - (stride % 4); //according to spec must be multiple of 4 + } + //use to debug + print_verbose("glTF: encoding type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); + + print_verbose("glTF: encoding accessor offset " + itos(byte_offset) + " view offset: " + itos(bv.byte_offset) + " total buffer len: " + itos(gltf_buffer.size()) + " view len " + itos(bv.byte_length)); + + const int buffer_end = (stride * (count - 1)) + _get_component_type_size(component_type); + // TODO define bv.byte_stride + bv.byte_offset = gltf_buffer.size(); + + switch (component_type) { + case COMPONENT_TYPE_BYTE: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 128.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int8_t))); + copymem(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int8_t)); + bv.byte_length = buffer.size() * sizeof(int8_t); + } break; + case COMPONENT_TYPE_UNSIGNED_BYTE: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 255.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + gltf_buffer.append_array(buffer); + bv.byte_length = buffer.size() * sizeof(uint8_t); + } break; + case COMPONENT_TYPE_SHORT: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 32768.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int16_t))); + copymem(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int16_t)); + bv.byte_length = buffer.size() * sizeof(int16_t); + } break; + case COMPONENT_TYPE_UNSIGNED_SHORT: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 65535.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint16_t))); + copymem(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint16_t)); + bv.byte_length = buffer.size() * sizeof(uint16_t); + } break; + case COMPONENT_TYPE_INT: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + buffer.write[dst_i] = d; + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int32_t))); + copymem(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int32_t)); + bv.byte_length = buffer.size() * sizeof(int32_t); + } break; + case COMPONENT_TYPE_FLOAT: { + Vector buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + buffer.write[dst_i] = d; + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(float))); + copymem(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_bufferview_i = bv.buffer = state.buffer_views.size(); + state.buffer_views.push_back(bv); + return OK; +} + +Error GLTFDocument::_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 = element_size; + if (bv.byte_stride != -1) { + stride = bv.byte_stride; + } + if (for_vertex && stride % 4) { + stride += 4 - (stride % 4); //according to spec must be multiple of 4 + } + + ERR_FAIL_INDEX_V(bv.buffer, state.buffers.size(), ERR_PARSE_ERROR); + + const uint32_t offset = bv.byte_offset + byte_offset; + Vector buffer = state.buffers[bv.buffer]; //copy on write, so no performance hit + const uint8_t *bufptr = buffer.ptr(); + + //use to debug + print_verbose("glTF: type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); + print_verbose("glTF: accessor offset " + itos(byte_offset) + " view offset: " + itos(bv.byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv.byte_length)); + + const int buffer_end = (stride * (count - 1)) + element_size; + ERR_FAIL_COND_V(buffer_end > bv.byte_length, ERR_PARSE_ERROR); + + ERR_FAIL_COND_V((int)(offset + buffer_end) > buffer.size(), ERR_PARSE_ERROR); + + //fill everything as doubles + + for (int i = 0; i < count; i++) { + + const uint8_t *src = &bufptr[offset + i * stride]; + + for (int j = 0; j < component_count; j++) { + + if (skip_every && j > 0 && (j % skip_every) == 0) { + src += skip_bytes; + } + + double d = 0; + + switch (component_type) { + case COMPONENT_TYPE_BYTE: { + int8_t b = int8_t(*src); + if (normalized) { + d = (double(b) / 128.0); + } else { + d = double(b); + } + } break; + case COMPONENT_TYPE_UNSIGNED_BYTE: { + uint8_t b = *src; + if (normalized) { + d = (double(b) / 255.0); + } else { + d = double(b); + } + } break; + case COMPONENT_TYPE_SHORT: { + int16_t s = *(int16_t *)src; + if (normalized) { + d = (double(s) / 32768.0); + } else { + d = double(s); + } + } break; + case COMPONENT_TYPE_UNSIGNED_SHORT: { + uint16_t s = *(uint16_t *)src; + if (normalized) { + d = (double(s) / 65535.0); + } else { + d = double(s); + } + + } break; + case COMPONENT_TYPE_INT: { + d = *(int *)src; + } break; + case COMPONENT_TYPE_FLOAT: { + d = *(float *)src; + } break; + } + + *dst++ = d; + src += component_size; + } + } + + return OK; +} + +int GLTFDocument::_get_component_type_size(const int component_type) { + + switch (component_type) { + case COMPONENT_TYPE_BYTE: 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 GLTFDocument::_decode_accessor(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + //spec, for reference: + //https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + + ERR_FAIL_INDEX_V(p_accessor, state.accessors.size(), Vector()); + + const GLTFAccessor &a = state.accessors[p_accessor]; + + const int component_count_for_type[7] = { + 1, 2, 3, 4, 4, 9, 16 + }; + + const int component_count = component_count_for_type[a.type]; + const int component_size = _get_component_type_size(a.component_type); + ERR_FAIL_COND_V(component_size == 0, Vector()); + int element_size = component_count * component_size; + + int skip_every = 0; + int skip_bytes = 0; + //special case of alignments, as described in spec + switch (a.component_type) { + case COMPONENT_TYPE_BYTE: + case COMPONENT_TYPE_UNSIGNED_BYTE: { + + if (a.type == TYPE_MAT2) { + skip_every = 2; + skip_bytes = 2; + element_size = 8; //override for this case + } + if (a.type == TYPE_MAT3) { + skip_every = 3; + skip_bytes = 1; + element_size = 12; //override for this case + } + + } break; + case COMPONENT_TYPE_SHORT: + case COMPONENT_TYPE_UNSIGNED_SHORT: { + if (a.type == TYPE_MAT3) { + skip_every = 6; + skip_bytes = 4; + element_size = 16; //override for this case + } + } break; + default: { + } + } + + Vector dst_buffer; + dst_buffer.resize(component_count * a.count); + double *dst = dst_buffer.ptrw(); + + if (a.buffer_view >= 0) { + + ERR_FAIL_INDEX_V(a.buffer_view, state.buffer_views.size(), Vector()); + + const Error err = _decode_buffer_view(state, dst, a.buffer_view, skip_every, skip_bytes, element_size, a.count, a.type, component_count, a.component_type, component_size, a.normalized, a.byte_offset, p_for_vertex); + if (err != OK) + return Vector(); + + } else { + //fill with zeros, as bufferview is not defined. + for (int i = 0; i < (a.count * component_count); i++) { + dst_buffer.write[i] = 0; + } + } + + if (a.sparse_count > 0) { + // I could not find any file using this, so this code is so far untested + Vector indices; + indices.resize(a.sparse_count); + const int indices_component_size = _get_component_type_size(a.sparse_indices_component_type); + + Error err = _decode_buffer_view(state, indices.ptrw(), a.sparse_indices_buffer_view, 0, 0, indices_component_size, a.sparse_count, TYPE_SCALAR, 1, a.sparse_indices_component_type, indices_component_size, false, a.sparse_indices_byte_offset, false); + if (err != OK) + return Vector(); + + Vector data; + data.resize(component_count * a.sparse_count); + err = _decode_buffer_view(state, data.ptrw(), a.sparse_values_buffer_view, skip_every, skip_bytes, element_size, a.sparse_count, a.type, component_count, a.component_type, component_size, a.normalized, a.sparse_values_byte_offset, p_for_vertex); + if (err != OK) + return Vector(); + + for (int i = 0; i < indices.size(); i++) { + const int write_offset = int(indices[i]) * component_count; + + for (int j = 0; j < component_count; j++) { + dst[write_offset + j] = data[i * component_count + j]; + } + } + } + + return dst_buffer; +} + +GLTFDocument::GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(GLTFState &state, const Array p_attribs, const bool p_for_vertex) { + + if (p_attribs.size() == 0) { + return -1; + } + const int ret_size = p_attribs.size(); + PoolVector attribs; + attribs.resize(ret_size); + PoolVector::Write w = attribs.write(); + Array type_max; + type_max.resize(1); + Array type_min; + type_min.resize(1); + for (int i = 0; i < p_attribs.size(); i++) { + w[i] = p_attribs[i]; + type_max[0] = MAX(double(w[i]), double(p_attribs[i])); + type_min[0] = MIN(double(w[i]), double(p_attribs[i])); + } + + ERR_FAIL_COND_V(attribs.size() == 0, -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_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.read().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; +} + +PoolVector GLTFDocument::_decode_accessor_as_ints(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + PoolVector ret; + + if (attribs.size() == 0) + return ret; + + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size(); + ret.resize(ret_size); + { + PoolVector::Write w = ret.write(); + for (int i = 0; i < ret_size; i++) { + w[i] = int(attribs_ptr[i]); + } + } + return ret; +} + +PoolVector GLTFDocument::_decode_accessor_as_floats(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + PoolVector ret; + + if (attribs.size() == 0) + return ret; + + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size(); + ret.resize(ret_size); + { + PoolVector::Write w = ret.write(); + for (int i = 0; i < ret_size; i++) { + w[i] = float(attribs_ptr[i]); + } + } + return ret; +} + +GLTFDocument::GLTFAccessorIndex +GLTFDocument::_encode_accessor_as_vec2(GLTFState &state, const Array p_attribs, const bool p_for_vertex) { + + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 2; + PoolVector attribs; + attribs.resize(ret_size); + Array type_max; + type_max.resize(2); + Array type_min; + type_min.resize(2); + { + PoolVector::Write w = attribs.write(); + for (int i = 0; i < p_attribs.size(); i++) { + Vector2 attrib = p_attribs[i]; + w[(i * 2) + 0] = attrib.x; + w[(i * 2) + 1] = attrib.y; + + type_max[0] = MAX(w[0], w[i * 2 + 0]); + type_min[0] = MIN(w[0], w[i * 2 + 0]); + + type_max[1] = MAX(w[1], w[i * 2 + 1]); + type_min[1] = MIN(w[1], w[i * 2 + 1]); + } + } + + ERR_FAIL_COND_V(attribs.size() % 2 != 0, -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_min; + 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.read().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; +} + +GLTFDocument::GLTFAccessorIndex +GLTFDocument::_encode_accessor_as_color(GLTFState &state, const Array p_attribs, const bool p_for_vertex) { + + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 4; + PoolVector attribs; + attribs.resize(ret_size); + + Array type_max; + type_max.resize(4); + Array type_min; + type_min.resize(4); + { + PoolVector::Write w = attribs.write(); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + w[(i * 4) + 0] = attrib.r; + w[(i * 4) + 1] = attrib.g; + w[(i * 4) + 2] = attrib.b; + w[(i * 4) + 3] = attrib.a; + + type_max[0] = MAX(w[0], w[i * 4 + 0]); + type_min[0] = MIN(w[0], w[i * 4 + 0]); + + type_max[1] = MAX(w[1], w[i * 4 + 1]); + type_min[1] = MIN(w[1], w[i * 4 + 1]); + + type_max[2] = MAX(w[2], w[i * 4 + 2]); + type_min[2] = MIN(w[2], w[i * 4 + 2]); + + type_max[3] = MAX(w[3], w[i * 4 + 3]); + type_min[3] = MIN(w[3], w[i * 4 + 3]); + } + } + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_min; + 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.read().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; +} + +GLTFDocument::GLTFAccessorIndex +GLTFDocument::_encode_accessor_as_weights(GLTFState &state, const Array p_attribs, const bool p_for_vertex) { + + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 4; + PoolVector attribs; + attribs.resize(ret_size); + + Array type_max; + type_max.resize(4); + Array type_min; + type_min.resize(4); + { + PoolVector::Write w = attribs.write(); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + w[(i * 4) + 0] = attrib.r; + type_max[0] = MAX(double(type_max[0]), w[(i * 4) + 0]); + type_min[0] = MIN(double(type_min[0]), w[(i * 4) + 0]); + + w[(i * 4) + 1] = attrib.g; + type_max[1] = MAX(double(type_max[1]), w[(i * 4) + 1]); + type_min[1] = MIN(double(type_min[1]), w[(i * 4) + 1]); + + w[(i * 4) + 2] = attrib.b; + type_max[2] = MAX(double(type_max[2]), w[(i * 4) + 2]); + type_min[2] = MIN(double(type_min[2]), w[(i * 4) + 2]); + + w[(i * 4) + 3] = attrib.a; + type_max[3] = MAX(double(type_max[3]), w[(i * 4) + 3]); + type_min[3] = MIN(double(type_min[3]), w[(i * 4) + 3]); + } + } + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_min; + 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.read().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; +} + +GLTFDocument::GLTFAccessorIndex +GLTFDocument::_encode_accessor_as_joints(GLTFState &state, const Array p_attribs, const bool p_for_vertex) { + + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 4; + PoolVector attribs; + attribs.resize(ret_size); + + Array type_max; + type_max.resize(4); + Array type_min; + type_min.resize(4); + { + PoolVector::Write w = attribs.write(); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + w[(i * 4) + 0] = attrib.r; + type_max[0] = MAX(double(type_max[0]), w[(i * 4) + 0]); + type_min[0] = MIN(double(type_min[0]), w[(i * 4) + 0]); + + w[(i * 4) + 1] = attrib.g; + type_max[1] = MAX(double(type_max[1]), w[(i * 4) + 1]); + type_min[1] = MIN(double(type_min[1]), w[(i * 4) + 1]); + + w[(i * 4) + 2] = attrib.b; + type_max[2] = MAX(double(type_max[2]), w[(i * 4) + 2]); + type_min[2] = MIN(double(type_min[2]), w[(i * 4) + 2]); + + w[(i * 4) + 3] = attrib.a; + type_max[3] = MAX(double(type_max[3]), w[(i * 4) + 3]); + type_min[3] = MIN(double(type_min[3]), w[(i * 4) + 3]); + } + } + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_min; + 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.read().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; +} + +GLTFDocument::GLTFAccessorIndex +GLTFDocument::_encode_accessor_as_quats(GLTFState &state, const Vector p_attribs, const bool p_for_vertex) { + + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 4; + PoolVector attribs; + attribs.resize(ret_size); + + Array type_max; + type_max.resize(4); + Array type_min; + type_min.resize(4); + { + PoolVector::Write w = attribs.write(); + for (int i = 0; i < p_attribs.size(); i++) { + Quat attrib = p_attribs[i]; + w[(i * 4) + 0] = attrib.x; + type_max[0] = MAX(double(type_max[0]), w[(i * 4) + 0]); + type_min[0] = MIN(double(type_min[0]), w[(i * 4) + 0]); + + w[(i * 4) + 1] = attrib.y; + type_max[1] = MAX(double(type_max[1]), w[(i * 4) + 1]); + type_min[1] = MIN(double(type_min[1]), w[(i * 4) + 1]); + + w[(i * 4) + 2] = attrib.z; + type_max[2] = MAX(double(type_max[2]), w[(i * 4) + 2]); + type_min[2] = MIN(double(type_min[2]), w[(i * 4) + 2]); + + w[(i * 4) + 3] = attrib.w; + type_max[3] = MAX(double(type_max[3]), w[(i * 4) + 3]); + type_min[3] = MIN(double(type_min[3]), w[(i * 4) + 3]); + } + } + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_min; + 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.read().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; +} + +PoolVector GLTFDocument::_decode_accessor_as_vec2(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + PoolVector ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 2 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 2; + ret.resize(ret_size); + { + PoolVector::Write w = ret.write(); + for (int i = 0; i < ret_size; i++) { + w[i] = Vector2(attribs_ptr[i * 2 + 0], attribs_ptr[i * 2 + 1]); + } + } + return ret; +} + +GLTFDocument::GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(GLTFState &state, const Array p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int ret_size = p_attribs.size(); + PoolVector attribs; + attribs.resize(ret_size); + PoolVector::Write w = attribs.write(); + + Array type_max; + type_max.resize(1); + Array type_min; + type_min.resize(1); + + for (int i = 0; i < p_attribs.size(); i++) { + w[i] = Math::stepify(p_attribs[i], 0.000001); + ; + type_max[0] = MAX(real_t(type_max[0]), w[i]); + type_min[0] = MIN(real_t(type_min[0]), w[i]); + } + + ERR_FAIL_COND_V(!attribs.size(), -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_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.read().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; +} + +GLTFDocument::GLTFAccessorIndex +GLTFDocument::_encode_accessor_as_vec3(GLTFState &state, const Array p_attribs, const bool p_for_vertex) { + + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 3; + PoolVector attribs; + attribs.resize(ret_size); + + Array type_max; + type_max.resize(3); + Array type_min; + type_min.resize(3); + + { + PoolVector::Write w = attribs.write(); + for (int i = 0; i < p_attribs.size(); i++) { + Vector3 attrib = p_attribs[i]; + w[(i * 3) + 0] = attrib.x; + type_max[0] = MAX(double(type_max[0]), w[(i * 3) + 0]); + type_min[0] = MIN(double(type_min[0]), w[(i * 3) + 0]); + + w[(i * 3) + 1] = attrib.y; + type_max[1] = MAX(double(type_max[1]), w[(i * 3) + 1]); + type_min[1] = MIN(double(type_min[1]), w[(i * 3) + 1]); + + w[(i * 3) + 2] = attrib.z; + type_max[2] = MAX(double(type_max[2]), w[(i * 3) + 2]); + type_min[2] = MIN(double(type_min[2]), w[(i * 3) + 2]); + } + } + + ERR_FAIL_COND_V(attribs.size() % 3 != 0, -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_min; + 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.read().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; +} + +GLTFDocument::GLTFAccessorIndex +GLTFDocument::_encode_accessor_as_xform(GLTFState &state, const Vector p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 16; + PoolVector attribs; + attribs.resize(ret_size); + + Array type_max; + type_max.resize(16); + type_max[3] = 0.0; + type_max[7] = 0.0; + type_max[11] = 0.0; + type_max[15] = 1.0; + Array type_min; + type_min.resize(16); + type_min[3] = 0.0; + type_min[7] = 0.0; + type_min[11] = 0.0; + type_min[15] = 1.0; + { + PoolVector::Write w = attribs.write(); + 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); + w[i * 16 + 0] = Math::stepify(axis_0.x, 0.000001); + w[i * 16 + 1] = Math::stepify(axis_0.y, 0.000001); + w[i * 16 + 2] = Math::stepify(axis_0.z, 0.000001); + w[i * 16 + 3] = 0.0; + + type_max[0] = MAX(double(type_max[0]), w[i * 16 + 0]); + type_max[1] = MAX(double(type_max[1]), w[i * 16 + 1]); + type_max[2] = MAX(double(type_max[2]), w[i * 16 + 2]); + type_max[3] = MAX(double(type_max[3]), w[i * 16 + 3]); + + type_min[0] = MIN(double(type_min[0]), w[i * 16 + 0]); + type_min[1] = MIN(double(type_min[1]), w[i * 16 + 1]); + type_min[2] = MIN(double(type_min[2]), w[i * 16 + 2]); + type_min[3] = MIN(double(type_min[3]), w[i * 16 + 3]); + + Vector3 axis_1 = basis.get_axis(Vector3::AXIS_Y); + w[i * 16 + 4] = Math::stepify(axis_1.x, 0.000001); + w[i * 16 + 5] = Math::stepify(axis_1.y, 0.000001); + w[i * 16 + 6] = Math::stepify(axis_1.z, 0.000001); + w[i * 16 + 7] = 0.0; + + type_max[4] = MAX(double(type_max[4]), w[i * 16 + 4]); + type_max[5] = MAX(double(type_max[5]), w[i * 16 + 5]); + type_max[6] = MAX(double(type_max[6]), w[i * 16 + 6]); + type_max[7] = MAX(double(type_max[7]), w[i * 16 + 7]); + + type_min[4] = MIN(double(type_min[4]), w[i * 16 + 4]); + type_min[5] = MIN(double(type_min[5]), w[i * 16 + 5]); + type_min[6] = MIN(double(type_min[6]), w[i * 16 + 6]); + type_min[7] = MIN(double(type_min[7]), w[i * 16 + 7]); + + Vector3 axis_2 = basis.get_axis(Vector3::AXIS_Z); + w[i * 16 + 8] = Math::stepify(axis_2.x, 0.000001); + w[i * 16 + 9] = Math::stepify(axis_2.y, 0.000001); + w[i * 16 + 10] = Math::stepify(axis_2.z, 0.000001); + w[i * 16 + 11] = 0.0; + + type_max[8] = MAX(double(type_max[8]), w[i * 16 + 8]); + type_max[9] = MAX(double(type_max[9]), w[i * 16 + 9]); + type_max[10] = MAX(double(type_max[10]), w[i * 16 + 10]); + type_max[11] = MAX(double(type_max[11]), w[i * 16 + 11]); + + type_min[8] = MIN(double(type_min[8]), w[i * 16 + 8]); + type_min[9] = MIN(double(type_min[9]), w[i * 16 + 9]); + type_min[10] = MIN(double(type_min[10]), w[i * 16 + 10]); + type_min[11] = MIN(double(type_min[11]), w[i * 16 + 11]); + + Vector3 origin = attrib.get_origin(); + w[i * 16 + 12] = Math::stepify(origin.x, 0.000001); + w[i * 16 + 13] = Math::stepify(origin.y, 0.000001); + w[i * 16 + 14] = Math::stepify(origin.z, 0.000001); + w[i * 16 + 15] = 1.0; + + type_max[12] = MAX(double(type_max[12]), w[i * 16 + 12]); + type_max[13] = MAX(double(type_max[13]), w[i * 16 + 13]); + type_max[14] = MAX(double(type_max[14]), w[i * 16 + 14]); + type_max[15] = MAX(double(type_max[15]), w[i * 16 + 15]); + + type_min[12] = MIN(double(type_min[12]), w[i * 16 + 12]); + type_min[13] = MIN(double(type_min[13]), w[i * 16 + 13]); + type_min[14] = MIN(double(type_min[14]), w[i * 16 + 14]); + type_min[15] = MIN(double(type_min[15]), w[i * 16 + 15]); + } + } + + ERR_FAIL_COND_V(attribs.size() % 16 != 0, -1); + + GLTFAccessor accessor; + 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; + + accessor.max = type_max; + accessor.min = type_min; + 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.read().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; +} + +PoolVector GLTFDocument::_decode_accessor_as_vec3(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + PoolVector ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 3 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 3; + ret.resize(ret_size); + { + PoolVector::Write w = ret.write(); + for (int i = 0; i < ret_size; i++) { + w[i] = Vector3(attribs_ptr[i * 3 + 0], attribs_ptr[i * 3 + 1], attribs_ptr[i * 3 + 2]); + } + } + return ret; +} + +PoolVector GLTFDocument::_decode_accessor_as_color(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + PoolVector ret; + + if (attribs.size() == 0) + return ret; + + const int type = state.accessors[p_accessor].type; + ERR_FAIL_COND_V(!(type == TYPE_VEC3 || type == TYPE_VEC4), ret); + int components; + if (type == TYPE_VEC3) { + components = 3; + } else { // TYPE_VEC4 + components = 4; + } + + ERR_FAIL_COND_V(attribs.size() % components != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / components; + ret.resize(ret_size); + { + PoolVector::Write w = ret.write(); + for (int i = 0; i < ret_size; i++) { + w[i] = Color(attribs_ptr[i * 4 + 0], attribs_ptr[i * 4 + 1], attribs_ptr[i * 4 + 2], components == 4 ? attribs_ptr[i * 4 + 3] : 1.0); + } + } + return ret; +} +Vector GLTFDocument::_decode_accessor_as_quat(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 4; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Quat(attribs_ptr[i * 4 + 0], attribs_ptr[i * 4 + 1], attribs_ptr[i * 4 + 2], attribs_ptr[i * 4 + 3]).normalized(); + } + } + return ret; +} +Vector GLTFDocument::_decode_accessor_as_xform2d(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); + ret.resize(attribs.size() / 4); + for (int i = 0; i < ret.size(); i++) { + ret.write[i][0] = Vector2(attribs[i * 4 + 0], attribs[i * 4 + 1]); + ret.write[i][1] = Vector2(attribs[i * 4 + 2], attribs[i * 4 + 3]); + } + return ret; +} + +Vector GLTFDocument::_decode_accessor_as_basis(GLTFState &state, const GLTFAccessorIndex p_accessor, bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 9 != 0, ret); + ret.resize(attribs.size() / 9); + for (int i = 0; i < ret.size(); i++) { + ret.write[i].set_axis(0, Vector3(attribs[i * 9 + 0], attribs[i * 9 + 1], attribs[i * 9 + 2])); + ret.write[i].set_axis(1, Vector3(attribs[i * 9 + 3], attribs[i * 9 + 4], attribs[i * 9 + 5])); + ret.write[i].set_axis(2, Vector3(attribs[i * 9 + 6], attribs[i * 9 + 7], attribs[i * 9 + 8])); + } + return ret; +} + +Vector GLTFDocument::_decode_accessor_as_xform(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + + const Vector attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 16 != 0, ret); + ret.resize(attribs.size() / 16); + for (int i = 0; i < ret.size(); i++) { + ret.write[i].basis.set_axis(0, Vector3(attribs[i * 16 + 0], attribs[i * 16 + 1], attribs[i * 16 + 2])); + ret.write[i].basis.set_axis(1, Vector3(attribs[i * 16 + 4], attribs[i * 16 + 5], attribs[i * 16 + 6])); + ret.write[i].basis.set_axis(2, Vector3(attribs[i * 16 + 8], attribs[i * 16 + 9], attribs[i * 16 + 10])); + ret.write[i].set_origin(Vector3(attribs[i * 16 + 12], attribs[i * 16 + 13], attribs[i * 16 + 14])); + } + return ret; +} + +Error GLTFDocument::_serialize_meshes(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 godot_mesh = state.meshes[gltf_mesh_i].mesh; + Array primitives; + Array targets; + Dictionary gltf_mesh; + Array target_names; + Array weights; + for (int surface_i = 0; surface_i < godot_mesh->get_surface_count(); surface_i++) { + Dictionary primitive; + Mesh::PrimitiveType primitive_type = godot_mesh->surface_get_primitive_type(surface_i); + 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["mode"] = primitives2[primitive_type]; + + Array array = godot_mesh->surface_get_arrays(surface_i); + Dictionary attributes; + { + Array a = array[Mesh::ARRAY_VERTEX]; + ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA); + attributes["POSITION"] = _encode_accessor_as_vec3(state, a, true); + } + { + Array a = array[Mesh::ARRAY_TANGENT]; + if (a.size()) { + const int ret_size = a.size() / 4; + Array attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + Vector3 tangent; + tangent.x = a[(i * 4) + 0]; + tangent.y = a[(i * 4) + 1]; + tangent.z = a[(i * 4) + 2]; + tangent.normalize(); + attribs[i] = Color(tangent.x, tangent.y, tangent.z, a[(i * 4) + 3]); + } + attributes["TANGENT"] = _encode_accessor_as_color(state, attribs, true); + } + } + { + PoolVector3Array a = array[Mesh::ARRAY_NORMAL]; + if (a.size()) { + const int ret_size = a.size(); + Array attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + attribs[i] = Vector3(a[i]).normalized(); + } + attributes["NORMAL"] = _encode_accessor_as_vec3(state, attribs, true); + } + } + { + Array a = array[Mesh::ARRAY_TEX_UV]; + if (a.size()) { + attributes["TEXCOORD_0"] = _encode_accessor_as_vec2(state, a, true); + } + } + { + Array a = array[Mesh::ARRAY_TEX_UV2]; + if (a.size()) { + + attributes["TEXCOORD_1"] = _encode_accessor_as_vec2(state, a, true); + } + } + { + Array a = array[Mesh::ARRAY_COLOR]; + if (a.size()) { + attributes["COLOR_0"] = _encode_accessor_as_color(state, a, true); + } + } + Map joint_i_to_bone_i; + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); node_i++) { + GLTFSkinIndex skin_i = -1; + if (state.nodes[node_i]->mesh == gltf_mesh_i) { + skin_i = state.nodes[node_i]->skin; + } + if (skin_i != -1) { + joint_i_to_bone_i = state.skins[skin_i].joint_i_to_bone_i; + break; + } + } + if (joint_i_to_bone_i.size()) { + Array a = array[Mesh::ARRAY_BONES]; + if (a.size()) { + const int ret_size = a.size() / 4; + Array attribs; + attribs.resize(ret_size); + { + Map bone_i_to_joint_i; + for (Map::Element *E = joint_i_to_bone_i.front(); E; E = E->next()) { + bone_i_to_joint_i.insert(E->get(), E->key()); + } + for (int i = 0; i < attribs.size(); i++) { + int32_t joint_0 = bone_i_to_joint_i[a[(i * 4) + 0]]; + int32_t joint_1 = bone_i_to_joint_i[a[(i * 4) + 1]]; + int32_t joint_2 = bone_i_to_joint_i[a[(i * 4) + 2]]; + int32_t joint_3 = bone_i_to_joint_i[a[(i * 4) + 3]]; + attribs[i] = Color(joint_0, joint_1, joint_2, joint_3); + } + } + attributes["JOINTS_0"] = _encode_accessor_as_joints(state, attribs, true); + } + } + if (joint_i_to_bone_i.size()) { + Array a = array[Mesh::ARRAY_WEIGHTS]; + if (a.size()) { + const int ret_size = a.size() / 4; + Array attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + attribs[i] = Color(a[(i * 4) + 0], a[(i * 4) + 1], a[(i * 4) + 2], a[(i * 4) + 3]); + } + attributes["WEIGHTS_0"] = _encode_accessor_as_weights(state, attribs, true); + } + } + { + Array 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[k + 0], mesh_indices[k + 2]); + } + } + primitive["indices"] = _encode_accessor_as_ints(state, mesh_indices, true); + + } else { + //generate indices because they need to be swapped for CW/CCW + const PoolVector &vertices = array[Mesh::ARRAY_VERTEX]; + ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR); + Array generated_indices; + const int vs = vertices.size(); + generated_indices.resize(vs); + { + for (int k = 0; k < vs; k += 3) { + generated_indices[k] = k; + generated_indices[k + 1] = k + 2; + generated_indices[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 (godot_mesh->get_blend_shape_count()) { + + Array array_morphs = godot_mesh->surface_get_blend_shape_arrays(surface_i); + for (int morph_i = 0; morph_i < array_morphs.size(); morph_i++) { + target_names.push_back(godot_mesh->get_blend_shape_name(morph_i)); + Dictionary t; + Array array_morph = array_morphs[morph_i]; + + Array varr = array_morph[Mesh::ARRAY_VERTEX]; + Ref array_mesh = godot_mesh; + if (varr.size() && array_mesh.is_valid()) { + ArrayMesh::BlendShapeMode shape_mode = array_mesh->get_blend_shape_mode(); + Vector src_varr = array[Mesh::ARRAY_VERTEX]; + if (shape_mode == ArrayMesh::BlendShapeMode::BLEND_SHAPE_MODE_NORMALIZED) { + const int max_idx = src_varr.size(); + for (int blend_i = 0; blend_i < max_idx; blend_i++) { + if (blend_i < max_idx) { + varr[blend_i] = Vector3(varr[blend_i]) - src_varr[blend_i]; + } else { + varr[blend_i] = src_varr[blend_i]; + } + } + } + + t["POSITION"] = _encode_accessor_as_vec3(state, varr, true); + } + + Array narr = array_morph[Mesh::ARRAY_NORMAL]; + if (varr.size() && array_mesh.is_valid()) { + t["NORMAL"] = _encode_accessor_as_vec3(state, narr, true); + } + Array tarr = array_morph[Mesh::ARRAY_TANGENT]; + if (tarr.size() && array_mesh.is_valid()) { + const int ret_size = tarr.size() / 4; + Array attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + attribs[i] = Color(tarr[(i * 4) + 0], tarr[(i * 4) + 1], tarr[(i * 4) + 2], tarr[(i * 4) + 3]); + } + t["TANGENT"] = _encode_accessor_as_joints(state, attribs, true); + } + targets.push_back(t); + } + } + + Ref mat = godot_mesh->surface_get_material(surface_i); + if (mat.is_valid()) { + Map, GLTFMaterialIndex>::Element *E = state.material_cache.find(mat); + if (E) { + primitive["material"] = E->get(); + } else { + state.material_cache.insert(mat, state.materials.size()); + primitive["material"] = state.materials.size(); + state.materials.push_back(mat); + } + } + + 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; + if (j < state.meshes[gltf_mesh_i].blend_weights.size()) { + weight = state.meshes[gltf_mesh_i].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(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 array_mesh; + array_mesh.instance(); + ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); + + Array primitives = d["primitives"]; + const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + + for (int j = 0; j < primitives.size(); j++) { + + Dictionary p = primitives[j]; + + Array array; + array.resize(Mesh::ARRAY_MAX); + + ERR_FAIL_COND_V(!p.has("attributes"), ERR_PARSE_ERROR); + + Dictionary a = p["attributes"]; + + Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; + if (p.has("mode")) { + const int mode = p["mode"]; + ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT); + static const Mesh::PrimitiveType primitives2[7] = { + Mesh::PRIMITIVE_POINTS, + Mesh::PRIMITIVE_LINES, + Mesh::PRIMITIVE_LINE_LOOP, + Mesh::PRIMITIVE_LINE_STRIP, + Mesh::PRIMITIVE_TRIANGLES, + Mesh::PRIMITIVE_TRIANGLE_STRIP, + Mesh::PRIMITIVE_TRIANGLE_FAN, + }; + + primitive = primitives2[mode]; + } + + ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR); + if (a.has("POSITION")) { + array[Mesh::ARRAY_VERTEX] = _decode_accessor_as_vec3(state, a["POSITION"], true); + } + if (a.has("NORMAL")) { + array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); + } + if (a.has("TANGENT")) { + array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(state, a["TANGENT"], true); + } + if (a.has("TEXCOORD_0")) { + array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(state, a["TEXCOORD_0"], true); + } + if (a.has("TEXCOORD_1")) { + array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(state, a["TEXCOORD_1"], true); + } + if (a.has("COLOR_0")) { + array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(state, a["COLOR_0"], true); + } + if (a.has("JOINTS_0")) { + array[Mesh::ARRAY_BONES] = _decode_accessor_as_ints(state, a["JOINTS_0"], true); + } + if (a.has("WEIGHTS_0")) { + PoolVector weights = _decode_accessor_as_floats(state, a["WEIGHTS_0"], true); + { //gltf does not seem to normalize the weights for some reason.. + int wc = weights.size(); + PoolVector::Write w = weights.write(); + + for (int k = 0; k < wc; k += 4) { + float total = 0.0; + total += w[k + 0]; + total += w[k + 1]; + total += w[k + 2]; + total += w[k + 3]; + if (total > 0.0) { + w[k + 0] /= total; + w[k + 1] /= total; + w[k + 2] /= total; + w[k + 3] /= total; + } + } + } + array[Mesh::ARRAY_WEIGHTS] = weights; + } + + if (p.has("indices")) { + PoolVector indices = _decode_accessor_as_ints(state, p["indices"], false); + + if (primitive == Mesh::PRIMITIVE_TRIANGLES) { + //swap around indices, convert ccw to cw for front face + + const int is = indices.size(); + const PoolVector::Write w = indices.write(); + for (int k = 0; k < is; k += 3) { + SWAP(w[k + 1], w[k + 2]); + } + } + array[Mesh::ARRAY_INDEX] = indices; + + } else if (primitive == Mesh::PRIMITIVE_TRIANGLES) { + //generate indices because they need to be swapped for CW/CCW + const PoolVector &vertices = array[Mesh::ARRAY_VERTEX]; + ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR); + PoolVector indices; + const int vs = vertices.size(); + indices.resize(vs); + { + const PoolVector::Write w = indices.write(); + for (int k = 0; k < vs; k += 3) { + w[k] = k; + w[k + 1] = k + 2; + w[k + 2] = k + 1; + } + } + array[Mesh::ARRAY_INDEX] = indices; + } + + bool generated_tangents = false; + Variant erased_indices; + + if (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("TEXCOORD_0") && a.has("NORMAL")) { + //must generate mikktspace tangents.. ergh.. + Ref st; + st.instance(); + st->create_from_triangle_arrays(array); + if (!p.has("targets")) { + //morph targets should not be reindexed, as array size might differ + //removing indices is the best bet here + st->deindex(); + erased_indices = a[Mesh::ARRAY_INDEX]; + a[Mesh::ARRAY_INDEX] = Variant(); + } + st->generate_tangents(); + array = st->commit_to_arrays(); + generated_tangents = true; + } + + 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? + array_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); + array_mesh->add_blend_shape(name); + } + } + + for (int k = 0; k < targets.size(); k++) { + + const Dictionary &t = targets[k]; + + Array array_copy; + array_copy.resize(Mesh::ARRAY_MAX); + + for (int l = 0; l < Mesh::ARRAY_MAX; l++) { + array_copy[l] = array[l]; + } + + array_copy[Mesh::ARRAY_INDEX] = Variant(); + + if (t.has("POSITION")) { + PoolVector varr = _decode_accessor_as_vec3(state, t["POSITION"], true); + const PoolVector src_varr = array[Mesh::ARRAY_VERTEX]; + const int size = src_varr.size(); + ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); + { + + const int max_idx = varr.size(); + varr.resize(size); + + const PoolVector::Write w_varr = varr.write(); + const PoolVector::Read r_varr = varr.read(); + const PoolVector::Read r_src_varr = src_varr.read(); + for (int l = 0; l < size; l++) { + if (l < max_idx) { + w_varr[l] = r_varr[l] + r_src_varr[l]; + } else { + w_varr[l] = r_src_varr[l]; + } + } + } + array_copy[Mesh::ARRAY_VERTEX] = varr; + } + if (t.has("NORMAL")) { + PoolVector narr = _decode_accessor_as_vec3(state, t["NORMAL"], true); + const PoolVector src_narr = array[Mesh::ARRAY_NORMAL]; + int size = src_narr.size(); + ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); + { + int max_idx = narr.size(); + narr.resize(size); + + const PoolVector::Write w_narr = narr.write(); + const PoolVector::Read r_narr = narr.read(); + const PoolVector::Read r_src_narr = src_narr.read(); + for (int l = 0; l < size; l++) { + if (l < max_idx) { + w_narr[l] = r_narr[l] + r_src_narr[l]; + } else { + w_narr[l] = r_src_narr[l]; + } + } + } + array_copy[Mesh::ARRAY_NORMAL] = narr; + } + if (t.has("TANGENT")) { + const PoolVector tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true); + const PoolVector src_tangents = array[Mesh::ARRAY_TANGENT]; + ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR); + + PoolVector tangents_v4; + + { + + int max_idx = tangents_v3.size(); + + int size4 = src_tangents.size(); + tangents_v4.resize(size4); + const PoolVector::Write w4 = tangents_v4.write(); + + const PoolVector::Read r3 = tangents_v3.read(); + const PoolVector::Read r4 = src_tangents.read(); + + for (int l = 0; l < size4 / 4; l++) { + + if (l < max_idx) { + w4[l * 4 + 0] = r3[l].x + r4[l * 4 + 0]; + w4[l * 4 + 1] = r3[l].y + r4[l * 4 + 1]; + w4[l * 4 + 2] = r3[l].z + r4[l * 4 + 2]; + } else { + w4[l * 4 + 0] = r4[l * 4 + 0]; + w4[l * 4 + 1] = r4[l * 4 + 1]; + w4[l * 4 + 2] = r4[l * 4 + 2]; + } + w4[l * 4 + 3] = r4[l * 4 + 3]; //copy flip value + } + } + + array_copy[Mesh::ARRAY_TANGENT] = tangents_v4; + } + + if (generated_tangents) { + Ref st; + st.instance(); + array_copy[Mesh::ARRAY_INDEX] = erased_indices; //needed for tangent generation, erased by deindex + 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 + array_mesh->add_surface_from_arrays(primitive, array, morphs); + + if (p.has("material")) { + const int material = p["material"]; + ERR_FAIL_INDEX_V(material, state.materials.size(), ERR_FILE_CORRUPT); + const Ref &mat = state.materials[material]; + + array_mesh->surface_set_material(array_mesh->get_surface_count() - 1, mat); + } + } + + GLTFMesh mesh; + mesh.mesh = array_mesh; + + if (d.has("weights")) { + const Array &weights = d["weights"]; + ERR_FAIL_COND_V(array_mesh->get_blend_shape_count() != weights.size(), ERR_PARSE_ERROR); + mesh.blend_weights.resize(weights.size()); + 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 GLTFDocument::_serialize_images(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 = state.images[i]->get_data(); + ERR_CONTINUE(image.is_null()); + + { + GLTFBufferViewIndex bvi; + + GLTFBufferView bv; + + const GLTFBufferIndex bi = 0; + bv.buffer = bi; + bv.byte_offset = state.buffers[bi].size(); + ERR_FAIL_INDEX_V(bi, state.buffers.size(), ERR_PARAMETER_RANGE_ERROR); + + PoolVector buffer; + 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); + copymem(&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"; + } + 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(GLTFState &state, const String &p_base_path) { + + if (!state.json.has("images")) + return OK; + + const Array &images = state.json["images"]; + for (int i = 0; i < images.size(); i++) { + + const Dictionary &d = images[i]; + + String mimetype; + if (d.has("mimeType")) { + mimetype = d["mimeType"]; + } + + Vector data; + const uint8_t *data_ptr = NULL; + int data_size = 0; + + if (d.has("uri")) { + String uri = d["uri"]; + + if (uri.findn("data:application/octet-stream;base64") == 0 || + uri.findn("data:" + mimetype + ";base64") == 0) { + //embedded data + data = _parse_base64_uri(uri); + data_ptr = data.ptr(); + data_size = data.size(); + } else { + + uri = p_base_path.plus_file(uri).replace("\\", "/"); //fix for windows + Ref texture = ResourceLoader::load(uri); + state.images.push_back(texture); + continue; + } + } + + if (d.has("bufferView")) { + 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; + } + + ERR_FAIL_COND_V(mimetype == "", ERR_FILE_CORRUPT); + + if (mimetype.findn("png") != -1) { + //is a png + const Ref img = Image::_png_mem_loader_func(data_ptr, data_size); + + ERR_FAIL_COND_V(img.is_null(), ERR_FILE_CORRUPT); + + Ref t; + t.instance(); + t->create_from_image(img); + + state.images.push_back(t); + continue; + } + + if (mimetype.findn("jpeg") != -1) { + //is a jpg + const Ref img = Image::_jpg_mem_loader_func(data_ptr, data_size); + + ERR_FAIL_COND_V(img.is_null(), ERR_FILE_CORRUPT); + + Ref t; + t.instance(); + t->create_from_image(img); + + state.images.push_back(t); + + continue; + } + + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + + print_verbose("Total images: " + itos(state.images.size())); + + return OK; +} + +Error GLTFDocument::_serialize_textures(GLTFState &state) { + + if (!state.textures.size()) { + return OK; + } + + Array textures; + for (int32_t i = 0; i < state.textures.size(); i++) { + Dictionary d; + GLTFTexture t = state.textures[i]; + ERR_CONTINUE(t.src_image == -1); + d["source"] = t.src_image; + textures.push_back(d); + } + state.json["textures"] = textures; + + return OK; +} + +Error GLTFDocument::_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; +} + +GLTFDocument::GLTFTextureIndex GLTFDocument::_set_texture(GLTFState &state, Ref p_texture) { + ERR_FAIL_COND_V(p_texture.is_null(), -1); + GLTFTexture gltf_texture; + ERR_FAIL_COND_V(p_texture->get_data().is_null(), -1); + gltf_texture.src_image = state.images.size(); + state.images.push_back(p_texture); + state.textures.push_back(gltf_texture); + return state.textures.size() - 1; +} + +Ref GLTFDocument::_get_texture(GLTFState &state, const GLTFTextureIndex p_texture) { + ERR_FAIL_INDEX_V(p_texture, state.textures.size(), Ref()); + const GLTFImageIndex image = state.textures[p_texture].src_image; + + ERR_FAIL_INDEX_V(image, state.images.size(), Ref()); + + return state.images[image]; +} + +Error GLTFDocument::_serialize_materials(GLTFState &state) { + + Array materials; + for (int32_t i = 0; i < state.materials.size(); i++) { + Dictionary d; + + Ref material = state.materials[i]; + if (material.is_null()) { + materials.push_back(d); + continue; + } + if (!material->get_name().empty()) { + d["name"] = _gen_unique_name(state, material->get_name()); + } + { + Dictionary mr; + { + Array arr; + const Color c = material->get_albedo(); + arr.push_back(c.r); + arr.push_back(c.g); + arr.push_back(c.b); + arr.push_back(c.a); + mr["baseColorFactor"] = arr; + } + { + Dictionary bct; + Ref albedo_texture = material->get_texture(SpatialMaterial::TEXTURE_ALBEDO); + GLTFTextureIndex gltf_texture_index = -1; + if (albedo_texture.is_valid() && albedo_texture->get_data().is_valid()) { + gltf_texture_index = _set_texture(state, albedo_texture); + } + if (gltf_texture_index != -1) { + bct["index"] = gltf_texture_index; + mr["baseColorTexture"] = bct; + } + } + + mr["metallicFactor"] = material->get_metallic(); + mr["roughnessFactor"] = material->get_roughness(); + bool has_roughness = material->get_texture(SpatialMaterial::TEXTURE_ROUGHNESS).is_valid() && material->get_texture(SpatialMaterial::TEXTURE_ROUGHNESS)->get_data().is_valid(); + bool has_ao = material->get_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION) && material->get_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION).is_valid(); + bool has_metalness = material->get_texture(SpatialMaterial::TEXTURE_METALLIC).is_valid() && material->get_texture(SpatialMaterial::TEXTURE_METALLIC)->get_data().is_valid(); + if (has_ao || has_roughness || has_metalness) { + Dictionary mrt; + Ref roughness_texture = material->get_texture(SpatialMaterial::TEXTURE_ROUGHNESS); + SpatialMaterial::TextureChannel roughness_channel = material->get_roughness_texture_channel(); + Ref metallic_texture = material->get_texture(SpatialMaterial::TEXTURE_METALLIC); + SpatialMaterial::TextureChannel metalness_channel = material->get_metallic_texture_channel(); + Ref ao_texture = material->get_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION); + SpatialMaterial::TextureChannel ao_channel = material->get_ao_texture_channel(); + Ref orm_texture; + orm_texture.instance(); + Ref orm_image; + orm_image.instance(); + int32_t height = 0; + int32_t width = 0; + Ref ao_image; + if (has_ao) { + height = ao_texture->get_height(); + width = ao_texture->get_width(); + ao_image = ao_texture->get_data(); + if (ao_image->is_compressed()) { + ao_image->decompress(); + } + } + Ref roughness_image; + if (has_roughness) { + height = roughness_texture->get_height(); + width = roughness_texture->get_width(); + roughness_image = roughness_texture->get_data(); + if (roughness_image->is_compressed()) { + roughness_image->decompress(); + } + } + Ref metallness_image; + if (has_metalness) { + height = metallic_texture->get_height(); + width = metallic_texture->get_width(); + metallness_image = metallic_texture->get_data(); + if (metallness_image->is_compressed()) { + metallness_image->decompress(); + } + } + Ref albedo_texture = material->get_texture(SpatialMaterial::TEXTURE_ALBEDO); + if (albedo_texture.is_valid() && albedo_texture->get_data().is_valid()) { + height = albedo_texture->get_height(); + width = albedo_texture->get_width(); + } + orm_image->create(width, height, false, Image::FORMAT_RGBA8); + orm_image->lock(); + for (int32_t h = 0; h < height; h++) { + for (int32_t w = 0; w < height; w++) { + Color c; + if (has_ao) { + if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) { + ao_image->lock(); + c.r = ao_image->get_pixel(w, h).to_srgb().r; + ao_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) { + ao_image->lock(); + c.r = ao_image->get_pixel(w, h).to_srgb().g; + ao_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) { + ao_image->lock(); + c.r = ao_image->get_pixel(w, h).to_srgb().b; + ao_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) { + ao_image->lock(); + c.r = ao_image->get_pixel(w, h).to_srgb().a; + ao_image->unlock(); + } + } + if (has_roughness) { + if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) { + roughness_image->lock(); + c.g = roughness_image->get_pixel(w, h).to_srgb().r; + roughness_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) { + roughness_image->lock(); + c.g = roughness_image->get_pixel(w, h).to_srgb().g; + roughness_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) { + roughness_image->lock(); + c.g = roughness_image->get_pixel(w, h).to_srgb().b; + roughness_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) { + roughness_image->lock(); + c.g = roughness_image->get_pixel(w, h).to_srgb().a; + roughness_image->unlock(); + } + } + if (has_metalness) { + if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) { + metallness_image->lock(); + c.b = metallness_image->get_pixel(w, h).to_srgb().r; + metallness_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) { + metallness_image->lock(); + c.b = metallness_image->get_pixel(w, h).to_srgb().g; + metallness_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) { + metallness_image->lock(); + c.b = metallness_image->get_pixel(w, h).to_srgb().b; + metallness_image->unlock(); + } else if (SpatialMaterial::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) { + metallness_image->lock(); + c.b = metallness_image->get_pixel(w, h).to_srgb().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_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; + mr["metallicRoughnessTexture"] = mrt; + } + } + + d["pbrMetallicRoughness"] = mr; + } + + if (material->get_feature(SpatialMaterial::FEATURE_NORMAL_MAPPING)) { + Dictionary nt; + Ref normal_texture = material->get_texture(SpatialMaterial::TEXTURE_NORMAL); + GLTFTextureIndex gltf_texture_index = -1; + if (normal_texture.is_valid() && normal_texture->get_data().is_valid()) { + gltf_texture_index = _set_texture(state, normal_texture); + } + nt["scale"] = material->get_normal_scale(); + if (gltf_texture_index != -1) { + nt["index"] = gltf_texture_index; + d["normalTexture"] = nt; + } + } + + if (material->get_feature(SpatialMaterial::FEATURE_EMISSION)) { + const Color c = material->get_emission().to_srgb(); + Array arr; + arr.push_back(c.r); + arr.push_back(c.g); + arr.push_back(c.b); + d["emissiveFactor"] = arr; + } + if (material->get_feature(SpatialMaterial::FEATURE_EMISSION)) { + Dictionary et; + Ref emission_texture = material->get_texture(SpatialMaterial::TEXTURE_EMISSION); + GLTFTextureIndex gltf_texture_index = -1; + if (emission_texture.is_valid() && emission_texture->get_data().is_valid()) { + 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)) { + 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(GLTFState &state) { + + if (!state.json.has("materials")) + return OK; + + const Array &materials = state.json["materials"]; + for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { + + const Dictionary &d = materials[i]; + + Ref material; + material.instance(); + if (d.has("name")) { + material->set_name(d["name"]); + } + + if (d.has("pbrMetallicRoughness")) { + + const Dictionary &mr = d["pbrMetallicRoughness"]; + if (mr.has("baseColorFactor")) { + const Array &arr = mr["baseColorFactor"]; + ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); + + material->set_albedo(c); + } + + if (mr.has("baseColorTexture")) { + const Dictionary &bct = mr["baseColorTexture"]; + if (bct.has("index")) { + material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, _get_texture(state, bct["index"])); + } + if (!mr.has("baseColorFactor")) { + material->set_albedo(Color(1, 1, 1)); + } + } + + if (mr.has("metallicFactor")) { + material->set_metallic(mr["metallicFactor"]); + } else { + material->set_metallic(1.0); + } + + if (mr.has("roughnessFactor")) { + material->set_roughness(mr["roughnessFactor"]); + } else { + material->set_roughness(1.0); + } + + if (mr.has("metallicRoughnessTexture")) { + const Dictionary &bct = mr["metallicRoughnessTexture"]; + if (bct.has("index")) { + const Ref t = _get_texture(state, bct["index"]); + material->set_texture(SpatialMaterial::TEXTURE_METALLIC, t); + material->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); + material->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, t); + material->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); + if (!mr.has("metallicFactor")) { + material->set_metallic(1); + } + if (!mr.has("roughnessFactor")) { + material->set_roughness(1); + } + } + } + } + + if (d.has("normalTexture")) { + const Dictionary &bct = d["normalTexture"]; + if (bct.has("index")) { + material->set_texture(SpatialMaterial::TEXTURE_NORMAL, _get_texture(state, bct["index"])); + material->set_feature(SpatialMaterial::FEATURE_NORMAL_MAPPING, true); + } + if (bct.has("scale")) { + material->set_normal_scale(bct["scale"]); + } + } + if (d.has("occlusionTexture")) { + const Dictionary &bct = d["occlusionTexture"]; + if (bct.has("index")) { + material->set_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION, _get_texture(state, bct["index"])); + material->set_ao_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_RED); + material->set_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION, true); + } + } + + if (d.has("emissiveFactor")) { + const Array &arr = d["emissiveFactor"]; + ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); + material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); + + material->set_emission(c); + } + + if (d.has("emissiveTexture")) { + const Dictionary &bct = d["emissiveTexture"]; + if (bct.has("index")) { + material->set_texture(SpatialMaterial::TEXTURE_EMISSION, _get_texture(state, bct["index"])); + material->set_feature(SpatialMaterial::FEATURE_EMISSION, true); + material->set_emission(Color(0, 0, 0)); + } + } + + if (d.has("doubleSided")) { + const bool ds = d["doubleSided"]; + if (ds) { + material->set_cull_mode(SpatialMaterial::CULL_DISABLED); + } + } + + if (d.has("alphaMode")) { + const String &am = d["alphaMode"]; + if (am != "OPAQUE") { + material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + material->set_depth_draw_mode(SpatialMaterial::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + } + } + + state.materials.push_back(material); + } + + print_verbose("Total materials: " + itos(state.materials.size())); + + return OK; +} + +GLTFDocument::GLTFNodeIndex GLTFDocument::_find_highest_node(GLTFState &state, const Vector &subset) { + int highest = -1; + GLTFNodeIndex best_node = -1; + + for (int i = 0; i < subset.size(); ++i) { + const GLTFNodeIndex node_i = subset[i]; + const GLTFNode *node = state.nodes[node_i]; + + if (highest == -1 || node->height < highest) { + highest = node->height; + best_node = node_i; + } + } + + return best_node; +} + +bool GLTFDocument::_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 GLTFDocument::_capture_nodes_for_multirooted_skin(GLTFState &state, GLTFSkin &skin) { + + DisjointSet disjoint_set; + + for (int i = 0; i < skin.joints.size(); ++i) { + const GLTFNodeIndex node_index = skin.joints[i]; + const GLTFNodeIndex parent = state.nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (skin.joints.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector roots; + disjoint_set.get_representatives(roots); + + if (roots.size() <= 1) { + return; + } + + int maxHeight = -1; + + // Determine the max height rooted tree + for (int i = 0; i < roots.size(); ++i) { + const GLTFNodeIndex root = roots[i]; + + if (maxHeight == -1 || state.nodes[root]->height < maxHeight) { + maxHeight = state.nodes[root]->height; + } + } + + // Go up the tree till all of the multiple roots of the skin are at the same hierarchy level. + // This sucks, but 99% of all game engines (not just Godot) would have this same issue. + for (int i = 0; i < roots.size(); ++i) { + + GLTFNodeIndex current_node = roots[i]; + while (state.nodes[current_node]->height > maxHeight) { + GLTFNodeIndex parent = state.nodes[current_node]->parent; + + if (state.nodes[parent]->joint && skin.joints.find(parent) < 0) { + skin.joints.push_back(parent); + } else if (skin.non_joints.find(parent) < 0) { + skin.non_joints.push_back(parent); + } + + current_node = parent; + } + + // replace the roots + roots.write[i] = current_node; + } + + // Climb up the tree until they all have the same parent + bool all_same; + + do { + all_same = true; + const GLTFNodeIndex first_parent = state.nodes[roots[0]]->parent; + + for (int i = 1; i < roots.size(); ++i) { + all_same &= (first_parent == state.nodes[roots[i]]->parent); + } + + if (!all_same) { + for (int i = 0; i < roots.size(); ++i) { + const GLTFNodeIndex current_node = roots[i]; + const GLTFNodeIndex parent = state.nodes[current_node]->parent; + + if (state.nodes[parent]->joint && skin.joints.find(parent) < 0) { + skin.joints.push_back(parent); + } else if (skin.non_joints.find(parent) < 0) { + skin.non_joints.push_back(parent); + } + + roots.write[i] = parent; + } + } + + } while (!all_same); +} + +Error GLTFDocument::_expand_skin(GLTFState &state, GLTFSkin &skin) { + + _capture_nodes_for_multirooted_skin(state, skin); + + // Grab all nodes that lay in between skin joints/nodes + DisjointSet disjoint_set; + + Vector all_skin_nodes; + all_skin_nodes.append_array(skin.joints); + all_skin_nodes.append_array(skin.non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state.nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector out_owners; + disjoint_set.get_representatives(out_owners); + + Vector out_roots; + + for (int i = 0; i < out_owners.size(); ++i) { + Vector set; + disjoint_set.get_members(set, out_owners[i]); + + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + out_roots.push_back(root); + } + + out_roots.sort(); + + for (int i = 0; i < out_roots.size(); ++i) { + _capture_nodes_in_skin(state, skin, out_roots[i]); + } + + skin.roots = out_roots; + + return OK; +} + +Error GLTFDocument::_verify_skin(GLTFState &state, GLTFSkin &skin) { + + // This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is) + // In case additional interpolating logic is added to the skins, this will help ensure that you + // do not cause it to self implode into a fiery blaze + + // We are going to re-calculate the root nodes and compare them to the ones saved in the skin, + // then ensure the multiple trees (if they exist) are on the same sublevel + + // Grab all nodes that lay in between skin joints/nodes + DisjointSet disjoint_set; + + Vector all_skin_nodes; + all_skin_nodes.append_array(skin.joints); + all_skin_nodes.append_array(skin.non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state.nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector out_owners; + disjoint_set.get_representatives(out_owners); + + Vector out_roots; + + for (int i = 0; i < out_owners.size(); ++i) { + Vector set; + disjoint_set.get_members(set, out_owners[i]); + + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + out_roots.push_back(root); + } + + out_roots.sort(); + + ERR_FAIL_COND_V(out_roots.size() == 0, FAILED); + + // Make sure the roots are the exact same (they better be) + ERR_FAIL_COND_V(out_roots.size() != skin.roots.size(), FAILED); + for (int i = 0; i < out_roots.size(); ++i) { + ERR_FAIL_COND_V(out_roots[i] != skin.roots[i], FAILED); + } + + // Single rooted skin? Perfectly ok! + if (out_roots.size() == 1) { + return OK; + } + + // Make sure all parents of a multi-rooted skin are the SAME + const GLTFNodeIndex parent = state.nodes[out_roots[0]]->parent; + for (int i = 1; i < out_roots.size(); ++i) { + if (state.nodes[out_roots[i]]->parent != parent) { + return FAILED; + } + } + + return OK; +} + +Error GLTFDocument::_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 GLTFDocument::_determine_skeletons(GLTFState &state) { + + // Using a disjoint set, we are going to potentially combine all skins that are actually branches + // of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton. + // This is another unclear issue caused by the current glTF specification. + + DisjointSet skeleton_sets; + + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + const GLTFSkin &skin = state.skins[skin_i]; + + Vector all_skin_nodes; + all_skin_nodes.append_array(skin.joints); + all_skin_nodes.append_array(skin.non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state.nodes[node_index]->parent; + skeleton_sets.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + skeleton_sets.create_union(parent, node_index); + } + } + + // We are going to connect the separate skin subtrees in each skin together + // so that the final roots are entire sets of valid skin trees + for (int i = 1; i < skin.roots.size(); ++i) { + skeleton_sets.create_union(skin.roots[0], skin.roots[i]); + } + } + + { // attempt to joint all touching subsets (siblings/parent are part of another skin) + Vector groups_representatives; + skeleton_sets.get_representatives(groups_representatives); + + Vector highest_group_members; + Vector > groups; + for (int i = 0; i < groups_representatives.size(); ++i) { + Vector group; + skeleton_sets.get_members(group, groups_representatives[i]); + highest_group_members.push_back(_find_highest_node(state, group)); + groups.push_back(group); + } + + for (int i = 0; i < highest_group_members.size(); ++i) { + const GLTFNodeIndex node_i = highest_group_members[i]; + + // Attach any siblings together (this needs to be done n^2/2 times) + for (int j = i + 1; j < highest_group_members.size(); ++j) { + const GLTFNodeIndex node_j = highest_group_members[j]; + + // Even if they are siblings under the root! :) + if (state.nodes[node_i]->parent == state.nodes[node_j]->parent) { + skeleton_sets.create_union(node_i, node_j); + } + } + + // Attach any parenting going on together (we need to do this n^2 times) + const GLTFNodeIndex node_i_parent = state.nodes[node_i]->parent; + if (node_i_parent >= 0) { + for (int j = 0; j < groups.size() && i != j; ++j) { + const Vector &group = groups[j]; + + if (group.find(node_i_parent) >= 0) { + const GLTFNodeIndex node_j = highest_group_members[j]; + skeleton_sets.create_union(node_i, node_j); + } + } + } + } + } + + // At this point, the skeleton groups should be finalized + Vector skeleton_owners; + skeleton_sets.get_representatives(skeleton_owners); + + // Mark all the skins actual skeletons, after we have merged them + for (GLTFSkeletonIndex skel_i = 0; skel_i < skeleton_owners.size(); ++skel_i) { + + const GLTFNodeIndex skeleton_owner = skeleton_owners[skel_i]; + GLTFSkeleton skeleton; + + Vector skeleton_nodes; + skeleton_sets.get_members(skeleton_nodes, skeleton_owner); + + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + GLTFSkin &skin = state.skins.write[skin_i]; + + // If any of the the skeletons nodes exist in a skin, that skin now maps to the skeleton + for (int i = 0; i < skeleton_nodes.size(); ++i) { + GLTFNodeIndex skel_node_i = skeleton_nodes[i]; + if (skin.joints.find(skel_node_i) >= 0 || skin.non_joints.find(skel_node_i) >= 0) { + skin.skeleton = skel_i; + continue; + } + } + } + + Vector non_joints; + for (int i = 0; i < skeleton_nodes.size(); ++i) { + const GLTFNodeIndex node_i = skeleton_nodes[i]; + + if (state.nodes[node_i]->joint) { + skeleton.joints.push_back(node_i); + } else { + non_joints.push_back(node_i); + } + } + + state.skeletons.push_back(skeleton); + + _reparent_non_joint_skeleton_subtrees(state, state.skeletons.write[skel_i], non_joints); + } + + for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); ++skel_i) { + GLTFSkeleton &skeleton = state.skeletons.write[skel_i]; + + for (int i = 0; i < skeleton.joints.size(); ++i) { + const GLTFNodeIndex node_i = skeleton.joints[i]; + GLTFNode *node = state.nodes[node_i]; + + ERR_FAIL_COND_V(!node->joint, ERR_PARSE_ERROR); + ERR_FAIL_COND_V(node->skeleton >= 0, ERR_PARSE_ERROR); + node->skeleton = skel_i; + } + + ERR_FAIL_COND_V(_determine_skeleton_roots(state, skel_i), ERR_PARSE_ERROR); + } + + return OK; +} + +Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(GLTFState &state, GLTFSkeleton &skeleton, const Vector &non_joints) { + + DisjointSet subtree_set; + + // Populate the disjoint set with ONLY non joints that are in the skeleton hierarchy (non_joints vector) + // This way we can find any joints that lie in between joints, as the current glTF specification + // mentions nothing about non-joints being in between joints of the same skin. Hopefully one day we + // can remove this code. + + // skinD depicted here explains this issue: + // https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Animation_Skin + + for (int i = 0; i < non_joints.size(); ++i) { + const GLTFNodeIndex node_i = non_joints[i]; + + subtree_set.insert(node_i); + + const GLTFNodeIndex parent_i = state.nodes[node_i]->parent; + if (parent_i >= 0 && non_joints.find(parent_i) >= 0 && !state.nodes[parent_i]->joint) { + subtree_set.create_union(parent_i, node_i); + } + } + + // Find all the non joint subtrees and re-parent them to a new "fake" joint + + Vector non_joint_subtree_roots; + subtree_set.get_representatives(non_joint_subtree_roots); + + for (int root_i = 0; root_i < non_joint_subtree_roots.size(); ++root_i) { + const GLTFNodeIndex subtree_root = non_joint_subtree_roots[root_i]; + + Vector subtree_nodes; + subtree_set.get_members(subtree_nodes, subtree_root); + + for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) { + ERR_FAIL_COND_V(_reparent_to_fake_joint(state, skeleton, subtree_nodes[subtree_i]), FAILED); + + // We modified the tree, recompute all the heights + _compute_node_heights(state); + } + } + + return OK; +} + +Error GLTFDocument::_reparent_to_fake_joint(GLTFState &state, GLTFSkeleton &skeleton, const GLTFNodeIndex node_index) { + GLTFNode *node = state.nodes[node_index]; + + // Can we just "steal" this joint if it is just a spatial node? + if (node->skin < 0 && node->mesh < 0 && node->camera < 0) { + node->joint = true; + // Add the joint to the skeletons joints + skeleton.joints.push_back(node_index); + return OK; + } + + GLTFNode *fake_joint = memnew(GLTFNode); + const GLTFNodeIndex fake_joint_index = state.nodes.size(); + state.nodes.push_back(fake_joint); + + // We better not be a joint, or we messed up in our logic + if (node->joint) + return FAILED; + + fake_joint->translation = node->translation; + fake_joint->rotation = node->rotation; + fake_joint->scale = node->scale; + fake_joint->xform = node->xform; + fake_joint->joint = true; + + // We can use the exact same name here, because the joint will be inside a skeleton and not the scene + fake_joint->name = node->name; + + // Clear the nodes transforms, since it will be parented to the fake joint + node->translation = Vector3(0, 0, 0); + node->rotation = Quat(); + node->scale = Vector3(1, 1, 1); + node->xform = Transform(); + + // Transfer the node children to the fake joint + for (int child_i = 0; child_i < node->children.size(); ++child_i) { + GLTFNode *child = state.nodes[node->children[child_i]]; + child->parent = fake_joint_index; + } + + fake_joint->children = node->children; + node->children.clear(); + + // add the fake joint to the parent and remove the original joint + if (node->parent >= 0) { + GLTFNode *parent = state.nodes[node->parent]; + parent->children.erase(node_index); + parent->children.push_back(fake_joint_index); + fake_joint->parent = node->parent; + } + + // Add the node to the fake joint + fake_joint->children.push_back(node_index); + node->parent = fake_joint_index; + node->fake_joint_parent = fake_joint_index; + + // Add the fake joint to the skeletons joints + skeleton.joints.push_back(fake_joint_index); + + // Replace skin_skeletons with fake joints if we must. + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + GLTFSkin &skin = state.skins.write[skin_i]; + if (skin.skin_root == node_index) { + skin.skin_root = fake_joint_index; + } + } + + return OK; +} + +Error GLTFDocument::_determine_skeleton_roots(GLTFState &state, const GLTFSkeletonIndex skel_i) { + + DisjointSet disjoint_set; + + for (GLTFNodeIndex i = 0; i < state.nodes.size(); ++i) { + const GLTFNode *node = state.nodes[i]; + + if (node->skeleton != skel_i) { + continue; + } + + disjoint_set.insert(i); + + if (node->parent >= 0 && state.nodes[node->parent]->skeleton == skel_i) { + disjoint_set.create_union(node->parent, i); + } + } + + GLTFSkeleton &skeleton = state.skeletons.write[skel_i]; + + Vector owners; + disjoint_set.get_representatives(owners); + + Vector roots; + + for (int i = 0; i < owners.size(); ++i) { + Vector set; + disjoint_set.get_members(set, owners[i]); + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + roots.push_back(root); + } + + roots.sort(); + + skeleton.roots = roots; + + if (roots.size() == 0) { + return FAILED; + } else if (roots.size() == 1) { + return OK; + } + + // Check that the subtrees have the same parent root + const GLTFNodeIndex parent = state.nodes[roots[0]]->parent; + for (int i = 1; i < roots.size(); ++i) { + if (state.nodes[roots[i]]->parent != parent) { + return FAILED; + } + } + + return OK; +} + +Error GLTFDocument::_create_skeletons(GLTFState &state) { + for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); ++skel_i) { + + GLTFSkeleton &gltf_skeleton = state.skeletons.write[skel_i]; + + Skeleton *skeleton = memnew(Skeleton); + gltf_skeleton.godot_skeleton = skeleton; + + // Make a unique name, no gltf node represents this skeleton + skeleton->set_name(_gen_unique_name(state, "Skeleton")); + + List bones; + + for (int i = 0; i < gltf_skeleton.roots.size(); ++i) { + bones.push_back(gltf_skeleton.roots[i]); + } + + // Make the skeleton creation deterministic by going through the roots in + // a sorted order, and DEPTH FIRST + bones.sort(); + + while (!bones.empty()) { + const GLTFNodeIndex node_i = bones.front()->get(); + bones.pop_front(); + + GLTFNode *node = state.nodes[node_i]; + ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED); + + { // Add all child nodes to the stack (deterministically) + Vector child_nodes; + for (int i = 0; i < node->children.size(); ++i) { + const GLTFNodeIndex child_i = node->children[i]; + if (state.nodes[child_i]->skeleton == skel_i) { + child_nodes.push_back(child_i); + } + } + + // Depth first insertion + child_nodes.sort(); + for (int i = child_nodes.size() - 1; i >= 0; --i) { + bones.push_front(child_nodes[i]); + } + } + + const int bone_index = skeleton->get_bone_count(); + + if (node->name.empty()) { + node->name = "bone"; + } + + node->name = _gen_unique_bone_name(state, skel_i, node->name); + + skeleton->add_bone(node->name); + skeleton->set_bone_rest(bone_index, node->xform); + + if (node->parent >= 0 && state.nodes[node->parent]->skeleton == skel_i) { + const int bone_parent = skeleton->find_bone(state.nodes[node->parent]->name); + ERR_FAIL_COND_V(bone_parent < 0, FAILED); + skeleton->set_bone_parent(bone_index, skeleton->find_bone(state.nodes[node->parent]->name)); + } + + state.scene_nodes.insert(node_i, skeleton); + } + } + + ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(state), ERR_PARSE_ERROR); + + return OK; +} + +Error GLTFDocument::_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]; + + 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 GLTFDocument::_serialize_skins(GLTFState &state) { + + Array json_skins; + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); node_i++) { + Dictionary json_skin; + Node *node = NULL; + if (state.nodes[node_i]->mesh == -1) { + continue; + } + node = state.scene_nodes[node_i]; + if (!node) { + continue; + } + MeshInstance *mi = Object::cast_to(node); + if (!mi) { + continue; + } + Skeleton *skeleton = Object::cast_to(mi->get_node(mi->get_skeleton_path())); + if (!skeleton) { + continue; + } + Ref skin = mi->get_skin(); + GLTFSkin gltf_skin; + Array json_joints; + + GLTFSkeletonIndex found_skel_i = -1; + for (GLTFSkeletonIndex skel_i = 0; skel_i < state.skeletons.size(); skel_i++) { + if (state.skeletons[skel_i].godot_skeleton == skeleton) { + found_skel_i = skel_i; + break; + } + } + for (int32_t bind_i = 0; bind_i < skin->get_bind_count(); bind_i++) { + int32_t bone_index = skin->get_bind_bone(bind_i); + GLTFNodeIndex node_index = state.skeletons[found_skel_i].godot_bone_node[bone_index]; + gltf_skin.joints.push_back(node_index); + gltf_skin.joints_original.push_back(node_index); + state.nodes[node_index]->joint = true; + gltf_skin.inverse_binds.push_back(skin->get_bind_pose(bind_i)); + json_joints.push_back(node_index); + // print_verbose("glTF: bind pose " + itos(bind_i) + " " + skin->get_bind_pose(bind_i)); + // print_verbose("glTF: bone rest " + itos(bone_index) + " " + skeleton->get_bone_rest(bone_index)); + } + + for (int32_t bone_i = 0; bone_i < skeleton->get_bone_count(); bone_i++) { + String bone_name = skeleton->get_bone_name(bone_i); + for (int32_t joint_i = 0; joint_i < gltf_skin.joints.size(); joint_i++) { + if (bone_name == state.nodes[gltf_skin.joints[joint_i]]->name) { + gltf_skin.joint_i_to_bone_i.insert(joint_i, bone_i); + continue; + } + } + } + for (Map::Element *E = state.scene_nodes.front(); E; E = E->next()) { + if (E->get() == skeleton) { + gltf_skin.skin_root = E->key(); + json_skin["skeleton"] = E->key(); + } + } + gltf_skin.godot_skin = skin; + gltf_skin.name = _gen_unique_name(state, skin->get_name()); + state.nodes.write[node_i]->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.name; + json_skins.push_back(json_skin); + } + state.json["skins"] = json_skins; + + // Purge the duplicates! + _remove_duplicate_skins(state); + + return OK; +} + +Error GLTFDocument::_create_skins(GLTFState &state) { + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + GLTFSkin &gltf_skin = state.skins.write[skin_i]; + + Ref skin; + skin.instance(); + + // Some skins don't have IBM's! What absolute monsters! + const bool has_ibms = !gltf_skin.inverse_binds.empty(); + + for (int joint_i = 0; joint_i < gltf_skin.joints_original.size(); ++joint_i) { + int bone_i = gltf_skin.joint_i_to_bone_i[joint_i]; + + if (has_ibms) { + skin->add_bind(bone_i, gltf_skin.inverse_binds[joint_i]); + } else { + skin->add_bind(bone_i, Transform()); + } + } + + gltf_skin.godot_skin = skin; + } + + // Purge the duplicates! + _remove_duplicate_skins(state); + + // Create unique names now, after removing duplicates + for (GLTFSkinIndex skin_i = 0; skin_i < state.skins.size(); ++skin_i) { + Ref skin = state.skins[skin_i].godot_skin; + if (skin->get_name().empty()) { + // Make a unique name, no gltf node represents this skin + skin->set_name(_gen_unique_name(state, "Skin")); + } + } + + return OK; +} + +bool GLTFDocument::_skins_are_same(const Ref &skin_a, const Ref &skin_b) { + if (skin_a->get_bind_count() != skin_b->get_bind_count()) { + return false; + } + + for (int i = 0; i < skin_a->get_bind_count(); ++i) { + + if (skin_a->get_bind_bone(i) != skin_b->get_bind_bone(i)) { + return false; + } + + 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(GLTFState &state) { + for (int i = 0; i < state.skins.size(); ++i) { + for (int j = i + 1; j < state.skins.size(); ++j) { + const Ref &skin_i = state.skins[i].godot_skin; + const Ref &skin_j = state.skins[j].godot_skin; + + if (_skins_are_same(skin_i, skin_j)) { + // replace it and delete the old + state.skins.write[j].godot_skin = skin_i; + } + } + } +} + +Error GLTFDocument::_serialize_cameras(GLTFState &state) { + + Array cameras; + for (GLTFCameraIndex i = 0; i < state.cameras.size(); i++) { + + Dictionary d; + + GLTFCamera camera = state.cameras[i]; + + if (camera.perspective == false) { + Dictionary og; + og["ymag"] = Math::deg2rad(camera.fov_size); + og["xmag"] = Math::deg2rad(camera.fov_size); + og["zfar"] = camera.zfar; + og["znear"] = camera.znear; + d["orthographic"] = og; + d["type"] = "orthographic"; + + } else if (camera.perspective) { + Dictionary ppt; + // GLTF spec is in radians, Godot's camera is in degrees. + ppt["yfov"] = Math::deg2rad(camera.fov_size); + ppt["zfar"] = camera.zfar; + ppt["znear"] = camera.znear; + d["perspective"] = ppt; + d["type"] = "perspective"; + } + + cameras.push_back(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_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"]; + // GLTF spec is in radians, Godot's camera is in degrees. + camera.fov_size = Math::rad2deg(real_t(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 = Math::rad2deg(real_t(ppt["yfov"])); + 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; +} + +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(GLTFState &state) { + + for (int32_t nodes_i = 0; nodes_i < state.scene_nodes.size(); nodes_i++) { + AnimationPlayer *ap = Object::cast_to(state.scene_nodes[nodes_i]); + if (!ap) { + continue; + } + List animation_names; + ap->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, ap, animation_names[animation_name_i]); + } + } + } + Array animations; + for (GLTFAnimationIndex animation_i = 0; animation_i < state.animations.size(); animation_i++) { + Dictionary d; + GLTFAnimation gltf_animation = state.animations[animation_i]; + if (!gltf_animation.tracks.size()) { + continue; + } + + if (!gltf_animation.name.empty()) { + d["name"] = gltf_animation.name; + } + Array channels; + Array samplers; + + for (Map::Element *E = gltf_animation.tracks.front(); E; E = E->next()) { + + GLTFAnimation::Track track = E->get(); + if (track.translation_track.times.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + s["interpolation"] = interpolation_to_string(track.translation_track.interpolation); + Array times = Variant(track.translation_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Array 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"] = E->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); + Array times = Variant(track.rotation_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Vector values = track.rotation_track.values; + s["output"] = _encode_accessor_as_quats(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "rotation"; + target["node"] = E->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); + Array times = Variant(track.scale_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Array 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"] = E->key(); + + t["target"] = target; + channels.push_back(t); + } + if (track.weight_tracks.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + Array times; + Array values; + + for (int32_t times_i = 0; times_i < track.weight_tracks[0].times.size(); times_i++) { + real_t time = track.weight_tracks[0].times[times_i]; + times.push_back(time); + } + + values.resize(times.size() * track.weight_tracks.size()); + // TODO Sort by order in blend shapes + for (int k = 0; k < track.weight_tracks.size(); k++) { + Vector wdata = track.weight_tracks[k].values; + for (int l = 0; l < wdata.size(); l++) { + values[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"] = E->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(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")) { + animation.name = d["name"]; + } + + for (int j = 0; j < channels.size(); j++) { + + const Dictionary &c = channels[j]; + if (!c.has("target")) + continue; + + const Dictionary &t = c["target"]; + if (!t.has("node") || !t.has("path")) { + continue; + } + + ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR); + const int sampler = c["sampler"]; + ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR); + + GLTFNodeIndex node = t["node"]; + String path = t["path"]; + + ERR_FAIL_INDEX_V(node, state.nodes.size(), ERR_PARSE_ERROR); + + GLTFAnimation::Track *track = nullptr; + + if (!animation.tracks.has(node)) { + animation.tracks[node] = GLTFAnimation::Track(); + } + + track = &animation.tracks[node]; + + const Dictionary &s = samplers[sampler]; + + ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR); + + const int input = s["input"]; + const int output = s["output"]; + + GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR; + int output_count = 1; + if (s.has("interpolation")) { + const String &in = s["interpolation"]; + if (in == "STEP") { + interp = GLTFAnimation::INTERP_STEP; + } else if (in == "LINEAR") { + interp = GLTFAnimation::INTERP_LINEAR; + } else if (in == "CATMULLROMSPLINE") { + interp = GLTFAnimation::INTERP_CATMULLROMSPLINE; + output_count = 3; + } else if (in == "CUBICSPLINE") { + interp = GLTFAnimation::INTERP_CUBIC_SPLINE; + output_count = 3; + } + } + + const PoolVector times = _decode_accessor_as_floats(state, input, false); + if (path == "translation") { + const PoolVector translations = _decode_accessor_as_vec3(state, output, false); + track->translation_track.interpolation = interp; + track->translation_track.times = Variant(times); //convert via variant + track->translation_track.values = Variant(translations); //convert via variant + } else if (path == "rotation") { + const Vector rotations = _decode_accessor_as_quat(state, output, false); + track->rotation_track.interpolation = interp; + track->rotation_track.times = Variant(times); //convert via variant + track->rotation_track.values = rotations; + } else if (path == "scale") { + const PoolVector scales = _decode_accessor_as_vec3(state, output, false); + track->scale_track.interpolation = interp; + track->scale_track.times = Variant(times); //convert via variant + track->scale_track.values = Variant(scales); //convert via variant + } else if (path == "weights") { + const PoolVector weights = _decode_accessor_as_floats(state, output, false); + + ERR_FAIL_INDEX_V(state.nodes[node]->mesh, state.meshes.size(), ERR_PARSE_ERROR); + const GLTFMesh *mesh = &state.meshes[state.nodes[node]->mesh]; + ERR_FAIL_COND_V(mesh->blend_weights.size() == 0, ERR_PARSE_ERROR); + const int wc = mesh->blend_weights.size(); + + track->weight_tracks.resize(wc); + + const int expected_value_count = times.size() * output_count * wc; + ERR_FAIL_COND_V_MSG(weights.size() != expected_value_count, ERR_PARSE_ERROR, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead."); + + const int wlen = weights.size() / wc; + PoolVector::Read r = weights.read(); + for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea + GLTFAnimation::Channel cf; + cf.interpolation = interp; + cf.times = Variant(times); + Vector wdata; + wdata.resize(wlen); + for (int l = 0; l < wlen; l++) { + wdata.write[l] = r[l * wc + k]; + } + + cf.values = wdata; + track->weight_tracks.write[k] = cf; + } + } else { + WARN_PRINTS("Invalid path '" + path + "'."); + } + } + + state.animations.push_back(animation); + } + + print_verbose("glTF: Total animations '" + itos(state.animations.size()) + "'."); + + return OK; +} + +void GLTFDocument::_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 *GLTFDocument::_generate_bone_attachment(GLTFState &state, Skeleton *skeleton, const GLTFNodeIndex node_index) { + + const GLTFNode *gltf_node = state.nodes[node_index]; + const GLTFNode *bone_node = state.nodes[gltf_node->parent]; + + 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; +} + +GLTFDocument::GLTFMeshIndex GLTFDocument::_convert_mesh_instance(GLTFState &state, MeshInstance *p_mesh_instance) { + GLTFMesh mesh; + if (p_mesh_instance->get_mesh().is_null()) { + return -1; + } + if (!p_mesh_instance->get_mesh()->get_surface_count()) { + return -1; + } + Ref mesh_mesh = p_mesh_instance->get_mesh(); + if (mesh_mesh.is_null()) { + return -1; + } + mesh.mesh = mesh_mesh; + for (int i = 0; i < mesh.mesh->get_surface_count(); i++) { + Ref material = p_mesh_instance->get_surface_material(i); + if (material.is_null()) { + continue; + } + mesh.mesh->surface_set_material(i, material); + } + for (int i = 0; i < mesh.mesh->get_surface_count(); i++) { + Ref material = p_mesh_instance->get_material_override(); + if (material.is_null()) { + continue; + } + mesh.mesh->surface_set_material(i, material); + } + print_verbose("glTF: Converting mesh: " + p_mesh_instance->get_name()); + for (int i = 0; i < mesh.mesh->get_blend_shape_count(); i++) { + float weight = p_mesh_instance->get("blend_shapes/" + _gen_unique_name(state, mesh.mesh->get_blend_shape_name(i))); + mesh.blend_weights.push_back(weight); + } + state.meshes.push_back(mesh); + return state.meshes.size() - 1; +} + +MeshInstance *GLTFDocument::_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().empty()) { + 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; +} + +Camera *GLTFDocument::_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; +} + +GLTFDocument::GLTFCameraIndex GLTFDocument::_convert_camera(GLTFState &state, Camera *p_camera) { + print_verbose("glTF: Converting camera: " + p_camera->get_name()); + + GLTFCamera c; + + if (p_camera->get_projection() == Camera::Projection::PROJECTION_PERSPECTIVE) { + c.perspective = true; + c.fov_size = p_camera->get_fov(); + c.zfar = p_camera->get_zfar(); + c.znear = p_camera->get_znear(); + } else { + c.fov_size = p_camera->get_fov(); + c.zfar = p_camera->get_zfar(); + c.znear = p_camera->get_znear(); + } + GLTFCameraIndex camera_index = state.cameras.size(); + state.cameras.push_back(c); + return camera_index; +} + +GLTFDocument::GLTFSkeletonIndex GLTFDocument::_convert_skeleton(GLTFState &state, Skeleton *p_skeleton, GLTFNode *p_node, GLTFNodeIndex p_node_index) { + print_verbose("glTF: Converting skeleton: " + p_skeleton->get_name()); + GLTFSkeleton gltf_skeleton; + gltf_skeleton.godot_skeleton = p_skeleton; + state.skeleton_to_node.insert(state.skeletons.size(), p_node_index); + state.skeletons.push_back(gltf_skeleton); + return state.skeletons.size() - 1; +} + +void GLTFDocument::_convert_spatial(GLTFState &state, Spatial *p_spatial, 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(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { + const GLTFNode *gltf_node = state.nodes[node_index]; + + Spatial *spatial = memnew(Spatial); + print_verbose("glTF: Converting spatial: " + gltf_node->name); + + return spatial; +} +void GLTFDocument::_convert_scene_node(GLTFState &state, Node *p_root_node, Node *p_scene_parent, const GLTFNodeIndex p_root_node_index, const GLTFNodeIndex p_parent_node_index) { + MeshInstance *mi = Object::cast_to(p_scene_parent); + Camera *camera = Object::cast_to(p_scene_parent); + Skeleton *skeleton = Object::cast_to(p_scene_parent); + Spatial *spatial = Object::cast_to(p_scene_parent); + Node2D *node_2d = Object::cast_to(p_scene_parent); + if (node_2d && !node_2d->is_visible()) { + return; + } + if (spatial && !spatial->is_visible()) { + return; + } + GLTFNodeIndex current_node_i = state.nodes.size(); + state.scene_nodes.insert(current_node_i, p_scene_parent); + GLTFDocument::GLTFNode *gltf_node = memnew(GLTFDocument::GLTFNode); + if (p_parent_node_index != -1) { + gltf_node->parent = p_parent_node_index; + state.nodes.write[p_parent_node_index]->children.push_back(current_node_i); + } + gltf_node->name = _gen_unique_name(state, p_scene_parent->get_name()); + if (mi) { + GLTFMeshIndex gltf_mesh_index = _convert_mesh_instance(state, mi); + if (gltf_mesh_index != -1) { + _convert_spatial(state, spatial, gltf_node); + gltf_node->mesh = gltf_mesh_index; + } + } else if (skeleton) { + GLTFSkeletonIndex gltf_skeleton_index = _convert_skeleton(state, skeleton, gltf_node, current_node_i); + if (gltf_skeleton_index != -1) { + gltf_node->skeleton = gltf_skeleton_index; + } + } else if (camera) { + GLTFCameraIndex camera_index = _convert_camera(state, camera); + if (camera_index != -1) { + _convert_spatial(state, spatial, gltf_node); + gltf_node->camera = camera_index; + } + } else if (spatial) { + _convert_spatial(state, spatial, gltf_node); + print_verbose(String("glTF: Converting spatial: ") + spatial->get_name()); + } else { + print_verbose(String("glTF: Converting node of type: ") + p_scene_parent->get_class_name()); + } + state.nodes.push_back(gltf_node); + + for (int node_i = 0; node_i < p_scene_parent->get_child_count(); node_i++) { + _convert_scene_node(state, p_root_node, p_scene_parent->get_child(node_i), p_root_node_index, current_node_i); + } +} + +void GLTFDocument::_generate_scene_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; + + // Is our parent a skeleton + Skeleton *active_skeleton = Object::cast_to(scene_parent); + + if (gltf_node->skeleton >= 0) { + Skeleton *skeleton = state.skeletons[gltf_node->skeleton].godot_skeleton; + + if (active_skeleton != skeleton) { + ERR_FAIL_COND_MSG(active_skeleton != nullptr, "glTF: Generating scene detected direct parented Skeletons"); + + // Add it to the scene if it has not already been added + if (skeleton->get_parent() == nullptr) { + scene_parent->add_child(skeleton); + skeleton->set_owner(scene_root); + } + } + + active_skeleton = skeleton; + current_node = skeleton; + } + + // If we have an active skeleton, and the node is node skinned, we need to create a bone attachment + if (current_node == nullptr && active_skeleton != nullptr && gltf_node->skin < 0) { + BoneAttachment *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index); + + scene_parent->add_child(bone_attachment); + bone_attachment->set_owner(scene_root); + + // There is no gltf_node that represent this, so just directly create a unique name + bone_attachment->set_name(_gen_unique_name(state, "BoneAttachment")); + + // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node + // and attach it to the bone_attachment + scene_parent = bone_attachment; + } + + // We still have not managed to make a node + if (current_node == nullptr) { + 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 { + 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); + 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]); + } +} + +template +struct EditorSceneImporterGLTFInterpolate { + + T lerp(const T &a, const T &b, float c) const { + + return a + (b - a) * c; + } + + T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) { + + const float t2 = t * t; + const float t3 = t2 * t; + + return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + } + + T bezier(T start, T control_1, T control_2, T end, float t) { + /* Formula from Wikipedia article on Bezier curves. */ + const real_t omt = (1.0 - t); + const real_t omt2 = omt * omt; + const real_t omt3 = omt2 * omt; + const real_t t2 = t * t; + const real_t t3 = t2 * t; + + return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; + } +}; + +// thank you for existing, partial specialization +template <> +struct EditorSceneImporterGLTFInterpolate { + + Quat lerp(const Quat &a, const Quat &b, const float c) const { + ERR_FAIL_COND_V(!a.is_normalized(), Quat()); + ERR_FAIL_COND_V(!b.is_normalized(), Quat()); + + 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(!p1.is_normalized(), Quat()); + ERR_FAIL_COND_V(!p2.is_normalized(), Quat()); + + 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(!start.is_normalized(), Quat()); + ERR_FAIL_COND_V(!end.is_normalized(), Quat()); + + return start.slerp(end, t).normalized(); + } +}; + +template +T GLTFDocument::_interpolate_track(const Vector &p_times, const Vector &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { + + //could use binary search, worth it? + int idx = -1; + for (int i = 0; i < p_times.size(); i++) { + if (p_times[i] > p_time) + break; + idx++; + } + + EditorSceneImporterGLTFInterpolate interp; + + switch (p_interp) { + case GLTFAnimation::INTERP_LINEAR: { + + if (idx == -1) { + return p_values[0]; + } else if (idx >= p_times.size() - 1) { + return p_values[p_times.size() - 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + return interp.lerp(p_values[idx], p_values[idx + 1], c); + + } break; + case GLTFAnimation::INTERP_STEP: { + + if (idx == -1) { + return p_values[0]; + } else if (idx >= p_times.size() - 1) { + return p_values[p_times.size() - 1]; + } + + return p_values[idx]; + + } break; + case GLTFAnimation::INTERP_CATMULLROMSPLINE: { + + if (idx == -1) { + return p_values[1]; + } else if (idx >= p_times.size() - 1) { + return p_values[1 + p_times.size() - 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + return interp.catmull_rom(p_values[idx - 1], p_values[idx], p_values[idx + 1], p_values[idx + 3], c); + + } break; + case GLTFAnimation::INTERP_CUBIC_SPLINE: { + + if (idx == -1) { + return p_values[1]; + } else if (idx >= p_times.size() - 1) { + return p_values[(p_times.size() - 1) * 3 + 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + const T from = p_values[idx * 3 + 1]; + const T c1 = from + p_values[idx * 3 + 2]; + const T to = p_values[idx * 3 + 4]; + const T c2 = to + p_values[idx * 3 + 3]; + + return interp.bezier(from, c1, c2, to, c); + + } break; + } + + ERR_FAIL_V(p_values[0]); +} + +void GLTFDocument::_import_animation(GLTFState &state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) { + + const GLTFAnimation &anim = state.animations[index]; + + String name = anim.name; + if (name.empty()) { + // No node represent these, and they are not in the hierarchy, so just make a unique name + name = _gen_unique_name(state, "Animation"); + } + + Ref animation; + animation.instance(); + animation->set_name(name); + + float length = 0; + + for (Map::Element *E = anim.tracks.front(); E; E = E->next()) { + + const GLTFAnimation::Track &track = E->get(); + //need to find the path + NodePath node_path; + + GLTFNodeIndex node_index = E->key(); + if (state.nodes[node_index]->fake_joint_parent >= 0) { + // Should be same as parent + node_index = state.nodes[node_index]->fake_joint_parent; + } + + const GLTFNode *node = state.nodes[E->key()]; + + if (node->skeleton >= 0) { + const Skeleton *sk = Object::cast_to(state.scene_nodes.find(node_index)->get()); + ERR_FAIL_COND(sk == nullptr); + + const String path = ap->get_parent()->get_path_to(sk); + const String bone = node->name; + node_path = path + ":" + bone; + } else { + node_path = ap->get_parent()->get_path_to(state.scene_nodes.find(node_index)->get()); + } + + for (int i = 0; i < track.rotation_track.times.size(); i++) { + length = MAX(length, track.rotation_track.times[i]); + } + for (int i = 0; i < track.translation_track.times.size(); i++) { + length = MAX(length, track.translation_track.times[i]); + } + for (int i = 0; i < track.scale_track.times.size(); i++) { + length = MAX(length, track.scale_track.times[i]); + } + + for (int i = 0; i < track.weight_tracks.size(); i++) { + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + length = MAX(length, track.weight_tracks[i].times[j]); + } + } + + if (track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) { + //make transform track + int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_TRANSFORM); + animation->track_set_path(track_idx, node_path); + //first determine animation length + + const float increment = 1.0 / float(bake_fps); + float time = 0.0; + + Vector3 base_pos; + Quat base_rot; + Vector3 base_scale = Vector3(1, 1, 1); + + if (!track.rotation_track.values.size()) { + base_rot = state.nodes[E->key()]->rotation.normalized(); + } + + if (!track.translation_track.values.size()) { + base_pos = state.nodes[E->key()]->translation; + } + + if (!track.scale_track.values.size()) { + base_scale = state.nodes[E->key()]->scale; + } + + bool last = false; + while (true) { + + Vector3 pos = base_pos; + Quat rot = base_rot; + Vector3 scale = base_scale; + + if (track.translation_track.times.size()) { + pos = _interpolate_track(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation); + } + + if (track.rotation_track.times.size()) { + rot = _interpolate_track(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); + } + + if (track.scale_track.times.size()) { + scale = _interpolate_track(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); + } + + if (node->skeleton >= 0) { + + Transform xform; + xform.basis.set_quat_scale(rot, scale); + xform.origin = pos; + + const Skeleton *skeleton = state.skeletons[node->skeleton].godot_skeleton; + const int bone_idx = skeleton->find_bone(node->name); + xform = skeleton->get_bone_rest(bone_idx).affine_inverse() * xform; + + rot = xform.basis.get_rotation_quat(); + rot.normalize(); + scale = xform.basis.get_scale(); + pos = xform.origin; + } + + animation->transform_track_insert_key(track_idx, time, pos, rot, scale); + + if (last) { + break; + } + time += increment; + if (time >= length) { + last = true; + time = length; + } + } + } + + for (int i = 0; i < track.weight_tracks.size(); i++) { + ERR_CONTINUE(node->mesh < 0 || node->mesh >= state.meshes.size()); + const GLTFMesh &mesh = state.meshes[node->mesh]; + const String prop = "blend_shapes/" + mesh.mesh->get_blend_shape_name(i); + + const String blend_path = String(node_path) + ":" + prop; + + const int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_VALUE); + animation->track_set_path(track_idx, blend_path); + + // Only LINEAR and STEP (NEAREST) can be supported out of the box by Godot's Animation, + // the other modes have to be baked. + GLTFAnimation::Interpolation gltf_interp = track.weight_tracks[i].interpolation; + if (gltf_interp == GLTFAnimation::INTERP_LINEAR || gltf_interp == GLTFAnimation::INTERP_STEP) { + animation->track_set_interpolation_type(track_idx, gltf_interp == GLTFAnimation::INTERP_STEP ? Animation::INTERPOLATION_NEAREST : Animation::INTERPOLATION_LINEAR); + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + const float t = track.weight_tracks[i].times[j]; + const float w = track.weight_tracks[i].values[j]; + animation->track_insert_key(track_idx, t, w); + } + } else { + // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. + const float increment = 1.0 / float(bake_fps); + float time = 0.0; + bool last = false; + while (true) { + _interpolate_track(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); + if (last) { + break; + } + time += increment; + if (time >= length) { + last = true; + time = length; + } + } + } + } + } + + animation->set_length(length); + + ap->add_animation(name, animation); +} + +void GLTFDocument::_convert_skeletons(GLTFState &state) { + for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < state.skeletons.size(); skeleton_i++) { + GLTFNodeIndex skeleton_node = state.skeleton_to_node[skeleton_i]; + for (int32_t bone_i = 0; bone_i < state.skeletons[skeleton_i].godot_skeleton->get_bone_count(); bone_i++) { + GLTFNode *node = memnew(GLTFNode); + node->name = _gen_unique_bone_name(state, skeleton_i, state.skeletons[skeleton_i].godot_skeleton->get_bone_name(bone_i)); + + Transform xform = state.skeletons[skeleton_i].godot_skeleton->get_bone_rest(bone_i); + node->scale = xform.basis.get_scale(); + node->rotation = xform.basis.get_rotation_quat(); + node->translation = xform.origin; + + int32_t parent = state.skeletons[skeleton_i].godot_skeleton->get_bone_parent(bone_i); + if (parent == -1) { + state.skeletons.write[skeleton_i].roots.push_back(state.nodes.size()); + node->parent = skeleton_node; + if (state.nodes[skeleton_node]->children.find(state.nodes.size()) == -1) { + state.nodes[skeleton_node]->children.push_back(state.nodes.size()); + } + } + state.skeletons.write[skeleton_i].godot_bone_node.insert(bone_i, state.nodes.size()); + state.nodes.push_back(node); + } + for (Map::Element *E = state.skeletons.write[skeleton_i].godot_bone_node.front(); E; E = E->next()) { + int32_t parent = state.skeletons.write[skeleton_i].godot_skeleton->get_bone_parent(E->key()); + if (parent == -1) { + continue; + } + GLTFNodeIndex node_i = state.skeletons[skeleton_i].godot_bone_node[E->key()]; + GLTFNodeIndex parent_i = state.skeletons[skeleton_i].godot_bone_node[parent]; + if (state.nodes[parent_i]->children.find(node_i) == -1) { + state.nodes[parent_i]->children.push_back(node_i); + } + } + } +} + +void GLTFDocument::_convert_mesh_instances(GLTFState &state) { + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { + GLTFNode *node = state.nodes[node_i]; + + if (node->mesh >= 0) { + Map::Element *mi_element = state.scene_nodes.find(node_i); + MeshInstance *mi = Object::cast_to(mi_element->get()); + ERR_FAIL_COND(mi == nullptr); + + Transform xform = mi->get_transform(); + node->scale = xform.basis.get_scale(); + node->rotation = xform.basis.get_rotation_quat(); + node->translation = xform.origin; + } + } +} + +void GLTFDocument::_process_mesh_instances(GLTFState &state, Node *scene_root) { + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); ++node_i) { + const GLTFNode *node = state.nodes[node_i]; + + if (node->skin >= 0 && node->mesh >= 0) { + const GLTFSkinIndex skin_i = node->skin; + + Map::Element *mi_element = state.scene_nodes.find(node_i); + MeshInstance *mi = Object::cast_to(mi_element->get()); + ERR_FAIL_COND(mi == nullptr); + + const GLTFSkeletonIndex skel_i = state.skins[node->skin].skeleton; + const GLTFSkeleton &gltf_skeleton = state.skeletons[skel_i]; + Skeleton *skeleton = gltf_skeleton.godot_skeleton; + ERR_FAIL_COND(skeleton == nullptr); + + 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()); + } + } +} + +GLTFDocument::GLTFAnimation::Track GLTFDocument::_convert_animation_track(GLTFDocument::GLTFState &state, Ref p_animation, Transform p_bone_rest, int32_t p_track_i, GLTFDocument::GLTFNodeIndex p_node_i) { + GLTFDocument::GLTFAnimation::Track track; + 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); + if (track_type == Animation::TYPE_TRANSFORM) { + Vector times; + int32_t key_count = p_animation->track_get_key_count(p_track_i); + times.resize(key_count); + 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); + } + track.translation_track.times = times; + track.translation_track.interpolation = gltf_interpolation; + track.rotation_track.times = times; + track.rotation_track.interpolation = gltf_interpolation; + track.scale_track.times = times; + track.scale_track.interpolation = gltf_interpolation; + + track.scale_track.values.resize(key_count); + track.scale_track.interpolation = gltf_interpolation; + track.translation_track.values.resize(key_count); + track.translation_track.interpolation = gltf_interpolation; + track.rotation_track.values.resize(key_count); + 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; + track.translation_track.values.write[key_i] = xform.get_origin(); + track.rotation_track.values.write[key_i] = xform.basis.get_rotation_quat(); + track.scale_track.values.write[key_i] = xform.basis.get_scale(); + } + } + + return track; +} + +void GLTFDocument::_convert_animation(GLTFState &state, AnimationPlayer *ap, String p_animation_track_name) { + + Ref animation = ap->get_animation(p_animation_track_name); + GLTFAnimation gltf_animation; + gltf_animation.name = p_animation_track_name; + Map node_node_index; + for (Map::Element *E = state.scene_nodes.front(); E; E = E->next()) { + ERR_CONTINUE(!E->get()); + node_node_index.insert(E->get()->get_name(), E->key()); + } + + 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); + NodePath path = orig_track_path; + if (String(path).find(":") != -1) { + path = String(path).split(":")[1]; + } + Map::Element *E = node_node_index.find(path.get_name(path.get_name_count() - 1)); + path.get_name(path.get_name_count() - 1); + if (E) { + GLTFAnimation::Track track = _convert_animation_track(state, animation, Transform(), track_i, E->get()); + gltf_animation.tracks.insert(E->get(), track); + } else if (String(orig_track_path).find(":") != -1 && String(orig_track_path).find(":blend_shapes/") == -1) { + const Vector node_suffix = String(orig_track_path).split(":"); + const String node = node_suffix[0]; + const NodePath node_path = node; + const String suffix = node_suffix[1]; + Node *godot_node = ap->get_owner()->get_node_or_null(node_path); + Skeleton *skeleton = Object::cast_to(godot_node); + if (!skeleton) { + continue; + } + int32_t bone = skeleton->find_bone(suffix); + Transform xform; + if (bone != -1) { + xform = skeleton->get_bone_rest(bone); + } + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); node_i++) { + if (state.nodes[node_i]->name == _sanitize_bone_name(suffix)) { + GLTFAnimation::Track track = _convert_animation_track(state, animation, xform, track_i, node_i); + gltf_animation.tracks.insert(node_i, track); + break; + } + } + } + } + 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(":blend_shapes/") != -1) { + const Vector node_suffix = String(orig_track_path).split(":blend_shapes/"); + const String node = node_suffix[0]; + const String suffix = node_suffix[1]; + const NodePath path = node; + Node *godot_node = ap->get_owner()->get_node_or_null(node); + MeshInstance *mi = Object::cast_to(godot_node); + if (!mi) { + continue; + } + Ref array_mesh = mi->get_mesh(); + if (array_mesh.is_null()) { + continue; + } + if (node_suffix.size() != 2) { + continue; + } + GLTFNodeIndex mesh_index = -1; + for (GLTFNodeIndex node_i = 0; node_i < state.nodes.size(); node_i++) { + String mesh_name = path.get_name(path.get_name_count() - 1); + if (state.nodes[node_i]->name == _sanitize_scene_name(mesh_name)) { + mesh_index = node_i; + break; + } + } + ERR_CONTINUE(mesh_index == -1); + Ref mesh = mi->get_mesh(); + ERR_CONTINUE(mesh.is_null()); + for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) { + if (mesh->get_blend_shape_name(shape_i) != suffix) { + continue; + } + GLTFDocument::GLTFAnimation::Track track; + Map::Element *E = gltf_animation.tracks.find(mesh_index); + if (E) { + track = E->get(); + } + Animation::InterpolationType interpolation = animation->track_get_interpolation_type(track_i); + + GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) { + gltf_interpolation = GLTFAnimation::INTERP_STEP; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) { + gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE; + } + Animation::TrackType track_type = animation->track_get_type(track_i); + if (track_type == Animation::TYPE_VALUE) { + int32_t key_count = animation->track_get_key_count(track_i); + GLTFAnimation::Channel weight; + weight.interpolation = gltf_interpolation; + weight.times.resize(key_count); + for (int32_t time_i = 0; time_i < key_count; time_i++) { + weight.times.write[time_i] = animation->track_get_key_time(track_i, time_i); + } + weight.values.resize(key_count); + for (int32_t value_i = 0; value_i < key_count; value_i++) { + weight.values.write[value_i] = animation->track_get_key_value(track_i, value_i); + } + track.weight_tracks.push_back(weight); + } + gltf_animation.tracks.insert(mesh_index, track); + } + } + } + if (gltf_animation.tracks.size()) { + state.animations.push_back(gltf_animation); + } +} + +Error GLTFDocument::parse(GLTFDocument::GLTFState *state, String p_path) { + + if (p_path.to_lower().ends_with("glb")) { + //binary file + //text file + Error err = _parse_glb(p_path, *state); + if (err) + return FAILED; + } else { + //text file + Error err = _parse_json(p_path, *state); + if (err) + return FAILED; + } + + 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 */ + Error 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 CAMERAS */ + err = _parse_cameras(*state); + if (err != OK) + return Error::FAILED; + + /* STEP 15 PARSE ANIMATIONS */ + err = _parse_animations(*state); + if (err != OK) + return Error::FAILED; + + /* STEP 16 ASSIGN SCENE NAMES */ + _assign_scene_names(*state); + + return OK; +} diff --git a/editor/import/editor_scene_importer_gltf.h b/modules/gltf/gltf_document.h similarity index 76% rename from editor/import/editor_scene_importer_gltf.h rename to modules/gltf/gltf_document.h index 6021bf10c86d..2ab43b903b3d 100644 --- a/editor/import/editor_scene_importer_gltf.h +++ b/modules/gltf/gltf_document.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* editor_scene_importer_gltf.h */ +/* gltf_document.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef EDITOR_SCENE_IMPORTER_GLTF_H -#define EDITOR_SCENE_IMPORTER_GLTF_H +#ifndef GLTF_DOCUMENT_H +#define GLTF_DOCUMENT_H #include "editor/import/resource_importer_scene.h" #include "scene/3d/skeleton.h" @@ -38,11 +38,10 @@ class AnimationPlayer; class BoneAttachment; class MeshInstance; +class GLTFDocument : public Resource { + GDCLASS(GLTFDocument, Resource); -class EditorSceneImporterGLTF : public EditorSceneImporter { - - GDCLASS(EditorSceneImporterGLTF, EditorSceneImporter); - +public: typedef int GLTFAccessorIndex; typedef int GLTFAnimationIndex; typedef int GLTFBufferIndex; @@ -56,29 +55,6 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { 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, @@ -88,9 +64,6 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { TYPE_MAT3, TYPE_MAT4, }; - - String _get_type_name(const GLTFType p_component); - struct GLTFNode { //matrices need to be transformed to this @@ -141,7 +114,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { buffer(-1), byte_offset(0), byte_length(0), - byte_stride(0), + byte_stride(-1), indices(false) { } }; @@ -154,8 +127,8 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { bool normalized; int count; GLTFType type; - float min; - float max; + Array min; + Array max; int sparse_count; int sparse_indices_buffer_view; int sparse_indices_byte_offset; @@ -169,8 +142,8 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { component_type = 0; normalized = false; count = 0; - min = 0; - max = 0; + min = Array(); + max = Array(); sparse_count = 0; sparse_indices_byte_offset = 0; sparse_values_byte_offset = 0; @@ -194,6 +167,8 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { // Set of unique bone names for the skeleton Set unique_names; + Map godot_bone_node; + GLTFSkeleton() : godot_skeleton(nullptr) { } @@ -239,7 +214,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { }; struct GLTFMesh { - Ref mesh; + Ref mesh; Vector blend_weights; }; @@ -300,6 +275,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { Vector accessors; Vector meshes; //meshes are loaded directly, no reason not to. + Map, GLTFMaterialIndex> material_cache; Vector > materials; String scene_name; @@ -314,6 +290,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { Set unique_names; Vector skeletons; + Map skeleton_to_node; Vector animations; Map scene_nodes; @@ -325,20 +302,49 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { } }; + 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: + String _get_component_type_name(const uint32_t p_component); + int _get_component_type_size(const int component_type); + + Error _parse_scenes(GLTFState &state); + + Error _parse_nodes(GLTFState &state); + + String _get_type_name(const GLTFType p_component); + String _get_accessor_type_name(const GLTFDocument::GLTFType p_type); + String _sanitize_scene_name(const String &name); String _gen_unique_name(GLTFState &state, const String &p_name); String _sanitize_bone_name(const String &name); - String _gen_unique_bone_name(GLTFState &state, const GLTFSkeletonIndex skel_i, const String &p_name); + String _gen_unique_bone_name(GLTFState &state, const GLTFDocument::GLTFSkeletonIndex skel_i, const String &p_name); - Ref _get_texture(GLTFState &state, const GLTFTextureIndex p_texture); + GLTFTextureIndex _set_texture(GLTFState &state, Ref p_texture); + Ref _get_texture(GLTFState &state, const GLTFDocument::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); @@ -359,6 +365,8 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { Vector _decode_accessor_as_xform(GLTFState &state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); Error _parse_meshes(GLTFState &state); + Error _serialize_textures(GLTFState &state); + Error _serialize_images(GLTFState &state, const String &p_path); Error _parse_images(GLTFState &state, const String &p_base_path); Error _parse_textures(GLTFState &state); @@ -379,39 +387,62 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { Error _create_skeletons(GLTFState &state); Error _map_skin_joints_indices_to_skeleton_bone_indices(GLTFState &state); - + Error _serialize_skins(GLTFState &state); Error _create_skins(GLTFState &state); bool _skins_are_same(const Ref &skin_a, const Ref &skin_b); void _remove_duplicate_skins(GLTFState &state); + Error _serialize_cameras(GLTFState &state); + Error _parse_cameras(GLTFState &state); Error _parse_animations(GLTFState &state); + Error _serialize_animations(GLTFState &state); + BoneAttachment *_generate_bone_attachment(GLTFState &state, Skeleton *skeleton, const GLTFNodeIndex node_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_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); - Spatial *_generate_scene(GLTFState &state, const int p_bake_fps); - - void _process_mesh_instances(GLTFState &state, Spatial *scene_root); - void _assign_scene_names(GLTFState &state); template T _interpolate_track(const Vector &p_times, const Vector &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp); - - void _import_animation(GLTFState &state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps); + GLTFAccessorIndex _encode_accessor_as_quats(GLTFState &state, const Vector p_attribs, const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_weights(GLTFState &state, const Array p_attribs, const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_joints(GLTFState &state, const Array p_attribs, const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_floats(GLTFState &state, const Array p_attribs, const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_vec2(GLTFState &state, const Array p_attribs, const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_vec3(GLTFState &state, const Array p_attribs, const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_color(GLTFState &state, const Array p_attribs, const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_ints(GLTFState &state, const Array p_attribs, const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_xform(GLTFState &state, const Vector p_attribs, const bool p_for_vertex); + Error _encode_buffer_view(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(GLTFDocument::GLTFState &state); + Error _encode_buffer_views(GLTFState &state); + Error _encode_buffers(GLTFState &state, const String &p_base_path); + Error _serialize_materials(GLTFState &state); + Error _serialize_meshes(GLTFState &state); + Error _serialize_nodes(GLTFState &state); + Error _serialize_scenes(GLTFState &state); + String interpolation_to_string(const GLTFAnimation::Interpolation p_interp); + GLTFAnimation::Track _convert_animation_track(GLTFDocument::GLTFState &state, Ref p_animation, Transform p_bone_rest, int32_t p_track_i, GLTFDocument::GLTFNodeIndex p_node_i); public: - virtual uint32_t get_import_flags() const; - virtual void get_extensions(List *r_extensions) const; - virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps = NULL, Error *r_err = NULL); - virtual Ref import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps); - - EditorSceneImporterGLTF(); + void _process_mesh_instances(GLTFState &state, Node *scene_root); + void _generate_scene_node(GLTFState &state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index); + void _import_animation(GLTFState &state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps); + GLTFMeshIndex _convert_mesh_instance(GLTFState &state, MeshInstance *p_mesh_instance); + void _convert_mesh_instances(GLTFState &state); + GLTFCameraIndex _convert_camera(GLTFState &state, Camera *p_camera); + void _convert_skeletons(GLTFState &state); + GLTFSkeletonIndex _convert_skeleton(GLTFState &state, Skeleton *p_skeleton, GLTFNode *p_node, GLTFNodeIndex p_node_index); + void _convert_spatial(GLTFState &state, Spatial *p_spatial, GLTFNode *p_node); + void _convert_scene_node(GLTFState &state, Node *_root_node, Node *p_root_node, const GLTFNodeIndex p_root_node_index, const GLTFNodeIndex p_parent_node_index); + void _convert_animation(GLTFState &state, AnimationPlayer *ap, String p_animation_track_name); + Error _serialize_json(const String &p_path, GLTFState &state); + Error parse(GLTFState *state, String p_path); }; - -#endif // EDITOR_SCENE_IMPORTER_GLTF_H +#endif diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp new file mode 100644 index 000000000000..6391def0893b --- /dev/null +++ b/modules/gltf/register_types.cpp @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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" + +#ifdef TOOLS_ENABLED +#include "editor/editor_node.h" +#include "editor_scene_exporter_gltf.h" +#include "editor_scene_exporter_gltf_plugin.h" +#include "editor_scene_importer_gltf.h" + +static void _editor_init() { + Ref import_gltf; + import_gltf.instance(); + Ref exporter_gltf; + exporter_gltf.instance(); + ResourceImporterScene::get_singleton()->add_importer(import_gltf); + ResourceExporterScene::get_singleton()->add_exporter(exporter_gltf); +} +#endif + +void register_gltf_types() { +#ifdef TOOLS_ENABLED + ClassDB::APIType prev_api = ClassDB::get_current_api(); + ClassDB::set_current_api(ClassDB::API_EDITOR); + + ClassDB::register_class(); + ClassDB::register_class(); + EditorPlugins::add_by_type(); + ClassDB::set_current_api(prev_api); + EditorNode::add_init_callback(_editor_init); + +#endif +} + +void unregister_gltf_types() { +} diff --git a/modules/gltf/register_types.h b/modules/gltf/register_types.h new file mode 100644 index 000000000000..09637da7c6ab --- /dev/null +++ b/modules/gltf/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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();