diff options
Diffstat (limited to 'modules')
249 files changed, 27354 insertions, 5489 deletions
diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index 6d69f4a2f4..1c42e6e008 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -398,14 +398,14 @@ XRPositionalTracker *ARKitInterface::get_anchor_for_uuid(const unsigned char *p_ } XRPositionalTracker *new_tracker = memnew(XRPositionalTracker); - new_tracker->set_type(XRServer::TRACKER_ANCHOR); + new_tracker->set_tracker_type(XRServer::TRACKER_ANCHOR); char tracker_name[256]; sprintf(tracker_name, "Anchor %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", p_uuid[0], p_uuid[1], p_uuid[2], p_uuid[3], p_uuid[4], p_uuid[5], p_uuid[6], p_uuid[7], p_uuid[8], p_uuid[9], p_uuid[10], p_uuid[11], p_uuid[12], p_uuid[13], p_uuid[14], p_uuid[15]); String name = tracker_name; print_line("Adding tracker " + name); - new_tracker->set_name(name); + new_tracker->set_tracker_name(name); // add our tracker XRServer::get_singleton()->add_tracker(new_tracker); diff --git a/modules/assimp/SCsub b/modules/assimp/SCsub deleted file mode 100644 index f1d0c742b4..0000000000 --- a/modules/assimp/SCsub +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python - -Import("env") -Import("env_modules") - -env_assimp = env_modules.Clone() - -# Force bundled version for now, there's no released version of Assimp with -# support for ArmaturePopulate which we use from their master branch. -if True: # env['builtin_assimp']: - thirdparty_dir = "#thirdparty/assimp" - - env_assimp.Prepend(CPPPATH=["#thirdparty/assimp"]) - env_assimp.Prepend(CPPPATH=["#thirdparty/assimp/code"]) - env_assimp.Prepend(CPPPATH=["#thirdparty/assimp/include"]) - - # env_assimp.Append(CPPDEFINES=['ASSIMP_DOUBLE_PRECISION']) # TODO default to what godot is compiled with for future double support - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_SINGLETHREADED"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_BOOST_WORKAROUND"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_OWN_ZLIB"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_EXPORT"]) - - # Importers we don't need - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_3D_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_3DS_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_3MF_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_AC_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_AMF_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_ASE_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_ASSBIN_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_B3D_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_BLEND_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_BVH_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_C4D_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_COB_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_COLLADA_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_CSM_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_DXF_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_GLTF2_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_GLTF_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_HMP_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_IFC_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_IRR_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_IRRMESH_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_LWO_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_LWS_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_M3D_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_MD2_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_MD3_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_MD5_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_MD5_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_MDC_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_MDL_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_MMD_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_MS3D_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_NDO_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_NFF_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_OBJ_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_OFF_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_OGRE_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_OPENGEX_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_PLY_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_Q3BSP_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_Q3D_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_RAW_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_SIB_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_SMD_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_STEP_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_STL_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_TERRAGEN_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_X3D_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_XGL_IMPORTER"]) - env_assimp.Append(CPPDEFINES=["ASSIMP_BUILD_NO_X_IMPORTER"]) - - if env["platform"] == "windows": - env_assimp.Append(CPPDEFINES=["PLATFORM_WINDOWS"]) - env_assimp.Append(CPPDEFINES=[("PLATFORM", "WINDOWS")]) - elif env["platform"] == "linuxbsd": - env_assimp.Append(CPPDEFINES=["PLATFORM_LINUX"]) - env_assimp.Append(CPPDEFINES=[("PLATFORM", "LINUX")]) - elif env["platform"] == "osx": - env_assimp.Append(CPPDEFINES=["PLATFORM_DARWIN"]) - env_assimp.Append(CPPDEFINES=[("PLATFORM", "DARWIN")]) - - env_thirdparty = env_assimp.Clone() - env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, Glob("#thirdparty/assimp/code/CApi/*.cpp")) - env_thirdparty.add_source_files(env.modules_sources, Glob("#thirdparty/assimp/code/Common/*.cpp")) - env_thirdparty.add_source_files(env.modules_sources, Glob("#thirdparty/assimp/code/PostProcessing/*.cpp")) - env_thirdparty.add_source_files(env.modules_sources, Glob("#thirdparty/assimp/code/Material/*.cpp")) - env_thirdparty.add_source_files(env.modules_sources, Glob("#thirdparty/assimp/code/FBX/*.cpp")) - -# Godot's own source files -env_assimp.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp deleted file mode 100644 index 796ee27a7d..0000000000 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ /dev/null @@ -1,1485 +0,0 @@ -/*************************************************************************/ -/* editor_scene_importer_assimp.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 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_assimp.h" -#include "core/io/image_loader.h" -#include "editor/import/resource_importer_scene.h" -#include "import_utils.h" -#include "scene/3d/camera_3d.h" -#include "scene/3d/light_3d.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/main/node.h" -#include "scene/resources/material.h" -#include "scene/resources/surface_tool.h" - -#include <assimp/matrix4x4.h> -#include <assimp/postprocess.h> -#include <assimp/scene.h> -#include <assimp/Importer.hpp> -#include <assimp/LogStream.hpp> - -// move into assimp -aiBone *get_bone_by_name(const aiScene *scene, aiString bone_name) { - for (unsigned int mesh_id = 0; mesh_id < scene->mNumMeshes; ++mesh_id) { - aiMesh *mesh = scene->mMeshes[mesh_id]; - - // iterate over all the bones on the mesh for this node only! - for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { - aiBone *bone = mesh->mBones[boneIndex]; - if (bone->mName == bone_name) { - printf("matched bone by name: %s\n", bone->mName.C_Str()); - return bone; - } - } - } - - return nullptr; -} - -void EditorSceneImporterAssimp::get_extensions(List<String> *r_extensions) const { - const String import_setting_string = "filesystem/import/open_asset_import/"; - - Map<String, ImportFormat> import_format; - { - Vector<String> exts; - exts.push_back("fbx"); - ImportFormat import = { exts, true }; - import_format.insert("fbx", import); - } - for (Map<String, ImportFormat>::Element *E = import_format.front(); E; E = E->next()) { - _register_project_setting_import(E->key(), import_setting_string, E->get().extensions, r_extensions, - E->get().is_default); - } -} - -void EditorSceneImporterAssimp::_register_project_setting_import(const String generic, const String import_setting_string, - const Vector<String> &exts, List<String> *r_extensions, - const bool p_enabled) const { - const String use_generic = "use_" + generic; - _GLOBAL_DEF(import_setting_string + use_generic, p_enabled, true); - if (ProjectSettings::get_singleton()->get(import_setting_string + use_generic)) { - for (int32_t i = 0; i < exts.size(); i++) { - r_extensions->push_back(exts[i]); - } - } -} - -uint32_t EditorSceneImporterAssimp::get_import_flags() const { - return IMPORT_SCENE; -} - -void EditorSceneImporterAssimp::_bind_methods() { -} - -Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, - List<String> *r_missing_deps, Error *r_err) { - Assimp::Importer importer; - importer.SetPropertyBool(AI_CONFIG_PP_FD_REMOVE, true); - // Cannot remove pivot points because the static mesh will be in the wrong place - importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); - int32_t max_bone_weights = 4; - //if (p_flags & IMPORT_ANIMATION_EIGHT_WEIGHTS) { - // const int eight_bones = 8; - // importer.SetPropertyBool(AI_CONFIG_PP_LBW_MAX_WEIGHTS, eight_bones); - // max_bone_weights = eight_bones; - //} - - importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT); - - //importer.SetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD, 1.0f); - int32_t post_process_Steps = aiProcess_CalcTangentSpace | - aiProcess_GlobalScale | - // imports models and listens to their file scale for CM to M conversions - //aiProcess_FlipUVs | - aiProcess_FlipWindingOrder | - // very important for culling so that it is done in the correct order. - //aiProcess_DropNormals | - //aiProcess_GenSmoothNormals | - //aiProcess_JoinIdenticalVertices | - aiProcess_ImproveCacheLocality | - //aiProcess_RemoveRedundantMaterials | // Causes a crash - //aiProcess_SplitLargeMeshes | - aiProcess_Triangulate | - aiProcess_GenUVCoords | - //aiProcess_FindDegenerates | - //aiProcess_SortByPType | - // aiProcess_FindInvalidData | - aiProcess_TransformUVCoords | - aiProcess_FindInstances | - //aiProcess_FixInfacingNormals | - //aiProcess_ValidateDataStructure | - aiProcess_OptimizeMeshes | - aiProcess_PopulateArmatureData | - //aiProcess_OptimizeGraph | - //aiProcess_Debone | - // aiProcess_EmbedTextures | - //aiProcess_SplitByBoneCount | - 0; - String g_path = ProjectSettings::get_singleton()->globalize_path(p_path); - aiScene *scene = (aiScene *)importer.ReadFile(g_path.utf8().ptr(), post_process_Steps); - - ERR_FAIL_COND_V_MSG(scene == nullptr, nullptr, String("Open Asset Import failed to open: ") + String(importer.GetErrorString())); - - return _generate_scene(p_path, scene, p_flags, p_bake_fps, max_bone_weights); -} - -template <class T> -struct EditorSceneImporterAssetImportInterpolate { - 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) { - float t2 = t * t; - float t3 = t2 * t; - - return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); - } - - T bezier(T start, T control_1, T control_2, T end, float t) { - /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - 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 EditorSceneImporterAssetImportInterpolate<Quat> { - Quat lerp(const Quat &a, const Quat &b, float c) const { - ERR_FAIL_COND_V_MSG(!a.is_normalized(), Quat(), "The quaternion \"a\" must be normalized."); - ERR_FAIL_COND_V_MSG(!b.is_normalized(), Quat(), "The quaternion \"b\" must be normalized."); - - return a.slerp(b, c).normalized(); - } - - Quat catmull_rom(const Quat &p0, const Quat &p1, const Quat &p2, const Quat &p3, float c) { - ERR_FAIL_COND_V_MSG(!p1.is_normalized(), Quat(), "The quaternion \"p1\" must be normalized."); - ERR_FAIL_COND_V_MSG(!p2.is_normalized(), Quat(), "The quaternion \"p2\" must be normalized."); - - return p1.slerp(p2, c).normalized(); - } - - Quat bezier(Quat start, Quat control_1, Quat control_2, Quat end, float t) { - ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quat(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quat(), "The end quaternion must be normalized."); - - return start.slerp(end, t).normalized(); - } -}; - -template <class T> -T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, - AssetImportAnimation::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++; - } - - EditorSceneImporterAssetImportInterpolate<T> interp; - - switch (p_interp) { - case AssetImportAnimation::INTERP_LINEAR: { - if (idx == -1) { - return p_values[0]; - } else if (idx >= p_times.size() - 1) { - return p_values[p_times.size() - 1]; - } - - 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 AssetImportAnimation::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 AssetImportAnimation::INTERP_CATMULLROMSPLINE: { - if (idx == -1) { - return p_values[1]; - } else if (idx >= p_times.size() - 1) { - return p_values[1 + p_times.size() - 1]; - } - - 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 AssetImportAnimation::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]; - } - - float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - T from = p_values[idx * 3 + 1]; - T c1 = from + p_values[idx * 3 + 2]; - T to = p_values[idx * 3 + 4]; - T c2 = to + p_values[idx * 3 + 3]; - - return interp.bezier(from, c1, c2, to, c); - - } break; - } - - ERR_FAIL_V(p_values[0]); -} - -aiBone *EditorSceneImporterAssimp::get_bone_from_stack(ImportState &state, aiString name) { - List<aiBone *>::Element *iter; - aiBone *bone = nullptr; - for (iter = state.bone_stack.front(); iter; iter = iter->next()) { - bone = (aiBone *)iter->get(); - - if (bone && bone->mName == name) { - state.bone_stack.erase(bone); - return bone; - } - } - - return nullptr; -} - -Node3D * -EditorSceneImporterAssimp::_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, - const int32_t p_max_bone_weights) { - ERR_FAIL_COND_V(scene == nullptr, nullptr); - - ImportState state; - state.path = p_path; - state.assimp_scene = scene; - state.max_bone_weights = p_max_bone_weights; - state.animation_player = nullptr; - state.import_flags = p_flags; - - // populate light map - for (unsigned int l = 0; l < scene->mNumLights; l++) { - aiLight *ai_light = scene->mLights[l]; - ERR_CONTINUE(ai_light == nullptr); - state.light_cache[AssimpUtils::get_assimp_string(ai_light->mName)] = l; - } - - // fill camera cache - for (unsigned int c = 0; c < scene->mNumCameras; c++) { - aiCamera *ai_camera = scene->mCameras[c]; - ERR_CONTINUE(ai_camera == nullptr); - state.camera_cache[AssimpUtils::get_assimp_string(ai_camera->mName)] = c; - } - - if (scene->mRootNode) { - state.nodes.push_back(scene->mRootNode); - - // make flat node tree - in order to make processing deterministic - for (unsigned int i = 0; i < scene->mRootNode->mNumChildren; i++) { - _generate_node(state, scene->mRootNode->mChildren[i]); - } - - RegenerateBoneStack(state); - - Node *last_valid_parent = nullptr; - - List<const aiNode *>::Element *iter; - for (iter = state.nodes.front(); iter; iter = iter->next()) { - const aiNode *element_assimp_node = iter->get(); - const aiNode *parent_assimp_node = element_assimp_node->mParent; - - String node_name = AssimpUtils::get_assimp_string(element_assimp_node->mName); - //print_verbose("node: " + node_name); - - Node3D *spatial = nullptr; - Transform transform = AssimpUtils::assimp_matrix_transform(element_assimp_node->mTransformation); - - // retrieve this node bone - aiBone *bone = get_bone_from_stack(state, element_assimp_node->mName); - - if (state.light_cache.has(node_name)) { - spatial = create_light(state, node_name, transform); - } else if (state.camera_cache.has(node_name)) { - spatial = create_camera(state, node_name, transform); - } else if (state.armature_nodes.find(element_assimp_node)) { - // create skeleton - print_verbose("Making skeleton: " + node_name); - Skeleton3D *skeleton = memnew(Skeleton3D); - spatial = skeleton; - if (!state.armature_skeletons.has(element_assimp_node)) { - state.armature_skeletons.insert(element_assimp_node, skeleton); - } - } else if (bone != nullptr) { - continue; - } else { - spatial = memnew(Node3D); - } - - ERR_CONTINUE_MSG(spatial == nullptr, "FBX Import - are we out of ram?"); - // we on purpose set the transform and name after creating the node. - - spatial->set_name(node_name); - spatial->set_global_transform(transform); - - // first element is root - if (iter == state.nodes.front()) { - state.root = spatial; - } - - // flat node map parent lookup tool - state.flat_node_map.insert(element_assimp_node, spatial); - - Map<const aiNode *, Node3D *>::Element *parent_lookup = state.flat_node_map.find(parent_assimp_node); - - // note: this always fails on the root node :) keep that in mind this is by design - if (parent_lookup) { - Node3D *parent_node = parent_lookup->value(); - - ERR_FAIL_COND_V_MSG(parent_node == nullptr, state.root, - "Parent node invalid even though lookup successful, out of ram?"); - - if (spatial != state.root) { - parent_node->add_child(spatial); - spatial->set_owner(state.root); - } else { - // required - think about it root never has a parent yet is valid, anything else without a parent is not valid. - } - } else if (spatial != state.root) { - // if the ainode is not in the tree - // parent it to the last good parent found - if (last_valid_parent) { - last_valid_parent->add_child(spatial); - spatial->set_owner(state.root); - } else { - // this is a serious error? - memdelete(spatial); - } - } - - // update last valid parent - last_valid_parent = spatial; - } - print_verbose("node counts: " + itos(state.nodes.size())); - - // make clean bone stack - RegenerateBoneStack(state); - - print_verbose("generating godot bone data"); - - print_verbose("Godot bone stack count: " + itos(state.bone_stack.size())); - - // This is a list of bones, duplicates are from other meshes and must be dealt with properly - for (List<aiBone *>::Element *element = state.bone_stack.front(); element; element = element->next()) { - aiBone *bone = element->get(); - - ERR_CONTINUE_MSG(!bone, "invalid bone read from assimp?"); - - // Utilities for armature lookup - for now only FBX makes these - aiNode *armature_for_bone = bone->mArmature; - - // Utilities for bone node lookup - for now only FBX makes these - aiNode *bone_node = bone->mNode; - aiNode *parent_node = bone_node->mParent; - - String bone_name = AssimpUtils::get_anim_string_from_assimp(bone->mName); - ERR_CONTINUE_MSG(armature_for_bone == nullptr, "Armature for bone invalid: " + bone_name); - Skeleton3D *skeleton = state.armature_skeletons[armature_for_bone]; - - state.skeleton_bone_map[bone] = skeleton; - - if (bone_name.empty()) { - bone_name = "untitled_bone_name"; - WARN_PRINT("Untitled bone name detected... report with file please"); - } - - // todo: this is where skin support goes - if (skeleton && skeleton->find_bone(bone_name) == -1) { - print_verbose("[Godot Glue] Imported bone" + bone_name); - int boneIdx = skeleton->get_bone_count(); - - Transform pform = AssimpUtils::assimp_matrix_transform(bone->mNode->mTransformation); - skeleton->add_bone(bone_name); - skeleton->set_bone_rest(boneIdx, pform); - - if (parent_node != nullptr) { - int parent_bone_id = skeleton->find_bone(AssimpUtils::get_anim_string_from_assimp(parent_node->mName)); - int current_bone_id = boneIdx; - skeleton->set_bone_parent(current_bone_id, parent_bone_id); - } - } - } - - print_verbose("generating mesh phase from skeletal mesh"); - - List<Node3D *> cleanup_template_nodes; - - for (Map<const aiNode *, Node3D *>::Element *key_value_pair = state.flat_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) { - const aiNode *assimp_node = key_value_pair->key(); - Node3D *mesh_template = key_value_pair->value(); - - ERR_CONTINUE(assimp_node == nullptr); - ERR_CONTINUE(mesh_template == nullptr); - - Node *parent_node = mesh_template->get_parent(); - - if (mesh_template == state.root) { - continue; - } - - if (parent_node == nullptr) { - print_error("Found invalid parent node!"); - continue; // root node - } - - String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); - Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); - - if (assimp_node->mNumMeshes > 0) { - MeshInstance3D *mesh = create_mesh(state, assimp_node, node_name, parent_node, node_transform); - if (mesh) { - parent_node->remove_child(mesh_template); - - // re-parent children - List<Node *> children; - // re-parent all children to new node - // note: since get_child_count will change during execution we must build a list first to be safe. - for (int childId = 0; childId < mesh_template->get_child_count(); childId++) { - // get child - Node *child = mesh_template->get_child(childId); - children.push_back(child); - } - - for (List<Node *>::Element *element = children.front(); element; element = element->next()) { - // reparent the children to the real mesh node. - mesh_template->remove_child(element->get()); - mesh->add_child(element->get()); - element->get()->set_owner(state.root); - } - - // update mesh in list so that each mesh node is available - // this makes the template unavailable which is the desired behaviour - state.flat_node_map[assimp_node] = mesh; - - cleanup_template_nodes.push_back(mesh_template); - - // clean up this list we don't need it - children.clear(); - } - } - } - - for (List<Node3D *>::Element *element = cleanup_template_nodes.front(); element; element = element->next()) { - if (element->get()) { - memdelete(element->get()); - } - } - } - - if (p_flags & IMPORT_ANIMATION && scene->mNumAnimations) { - state.animation_player = memnew(AnimationPlayer); - state.root->add_child(state.animation_player); - state.animation_player->set_owner(state.root); - - for (uint32_t i = 0; i < scene->mNumAnimations; i++) { - _import_animation(state, i, p_bake_fps); - } - } - - // - // Cleanup operations - // - - state.mesh_cache.clear(); - state.material_cache.clear(); - state.light_cache.clear(); - state.camera_cache.clear(); - state.assimp_node_map.clear(); - state.path_to_image_cache.clear(); - state.nodes.clear(); - state.flat_node_map.clear(); - state.armature_skeletons.clear(); - state.bone_stack.clear(); - return state.root; -} - -void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int track_id, - int anim_fps, Ref<Animation> animation, float ticks_per_second, - Skeleton3D *skeleton, const NodePath &node_path, - const String &node_name, aiBone *track_bone) { - const aiNodeAnim *assimp_track = assimp_anim->mChannels[track_id]; - //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 - - float increment = 1.0 / float(anim_fps); - float time = 0.0; - - bool last = false; - - Vector<Vector3> pos_values; - Vector<float> pos_times; - Vector<Vector3> scale_values; - Vector<float> scale_times; - Vector<Quat> rot_values; - Vector<float> rot_times; - - for (size_t p = 0; p < assimp_track->mNumPositionKeys; p++) { - aiVector3D pos = assimp_track->mPositionKeys[p].mValue; - pos_values.push_back(Vector3(pos.x, pos.y, pos.z)); - pos_times.push_back(assimp_track->mPositionKeys[p].mTime / ticks_per_second); - } - - for (size_t r = 0; r < assimp_track->mNumRotationKeys; r++) { - aiQuaternion quat = assimp_track->mRotationKeys[r].mValue; - rot_values.push_back(Quat(quat.x, quat.y, quat.z, quat.w).normalized()); - rot_times.push_back(assimp_track->mRotationKeys[r].mTime / ticks_per_second); - } - - for (size_t sc = 0; sc < assimp_track->mNumScalingKeys; sc++) { - aiVector3D scale = assimp_track->mScalingKeys[sc].mValue; - scale_values.push_back(Vector3(scale.x, scale.y, scale.z)); - scale_times.push_back(assimp_track->mScalingKeys[sc].mTime / ticks_per_second); - } - - while (true) { - Vector3 pos; - Quat rot; - Vector3 scale(1, 1, 1); - - if (pos_values.size()) { - pos = _interpolate_track<Vector3>(pos_times, pos_values, time, AssetImportAnimation::INTERP_LINEAR); - } - - if (rot_values.size()) { - rot = _interpolate_track<Quat>(rot_times, rot_values, time, - AssetImportAnimation::INTERP_LINEAR) - .normalized(); - } - - if (scale_values.size()) { - scale = _interpolate_track<Vector3>(scale_times, scale_values, time, AssetImportAnimation::INTERP_LINEAR); - } - - if (skeleton) { - int skeleton_bone = skeleton->find_bone(node_name); - - if (skeleton_bone >= 0 && track_bone) { - Transform xform; - xform.basis.set_quat_scale(rot, scale); - xform.origin = pos; - - xform = skeleton->get_bone_rest(skeleton_bone).inverse() * xform; - - rot = xform.basis.get_rotation_quat(); - rot.normalize(); - scale = xform.basis.get_scale(); - pos = xform.origin; - } else { - ERR_FAIL_MSG("Skeleton bone lookup failed for skeleton: " + skeleton->get_name()); - } - } - - animation->track_set_interpolation_type(track_idx, Animation::INTERPOLATION_LINEAR); - animation->transform_track_insert_key(track_idx, time, pos, rot, scale); - - if (last) { //done this way so a key is always inserted past the end (for proper interpolation) - break; - } - time += increment; - if (time >= animation->get_length()) { - last = true; - } - } -} - -// I really do not like this but need to figure out a better way of removing it later. -Node *EditorSceneImporterAssimp::get_node_by_name(ImportState &state, String name) { - for (Map<const aiNode *, Node3D *>::Element *key_value_pair = state.flat_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) { - const aiNode *assimp_node = key_value_pair->key(); - Node3D *node = key_value_pair->value(); - - String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); - if (name == node_name && node) { - return node; - } - } - return nullptr; -} - -/* Bone stack is a fifo handler for multiple armatures since armatures aren't a thing in assimp (yet) */ -void EditorSceneImporterAssimp::RegenerateBoneStack(ImportState &state) { - state.bone_stack.clear(); - // build bone stack list - for (unsigned int mesh_id = 0; mesh_id < state.assimp_scene->mNumMeshes; ++mesh_id) { - aiMesh *mesh = state.assimp_scene->mMeshes[mesh_id]; - - // iterate over all the bones on the mesh for this node only! - for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { - aiBone *bone = mesh->mBones[boneIndex]; - - // doubtful this is required right now but best to check - if (!state.bone_stack.find(bone)) { - //print_verbose("[assimp] bone stack added: " + String(bone->mName.C_Str()) ); - state.bone_stack.push_back(bone); - } - } - } -} - -/* Bone stack is a fifo handler for multiple armatures since armatures aren't a thing in assimp (yet) */ -void EditorSceneImporterAssimp::RegenerateBoneStack(ImportState &state, aiMesh *mesh) { - state.bone_stack.clear(); - // iterate over all the bones on the mesh for this node only! - for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { - aiBone *bone = mesh->mBones[boneIndex]; - if (state.bone_stack.find(bone) == nullptr) { - state.bone_stack.push_back(bone); - } - } -} - -// animation tracks are per bone - -void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_animation_index, int p_bake_fps) { - ERR_FAIL_INDEX(p_animation_index, (int)state.assimp_scene->mNumAnimations); - - const aiAnimation *anim = state.assimp_scene->mAnimations[p_animation_index]; - String name = AssimpUtils::get_anim_string_from_assimp(anim->mName); - if (name == String()) { - name = "Animation " + itos(p_animation_index + 1); - } - print_verbose("import animation: " + name); - float ticks_per_second = anim->mTicksPerSecond; - - if (state.assimp_scene->mMetaData != nullptr && Math::is_equal_approx(ticks_per_second, 0.0f)) { - int32_t time_mode = 0; - state.assimp_scene->mMetaData->Get("TimeMode", time_mode); - ticks_per_second = AssimpUtils::get_fbx_fps(time_mode, state.assimp_scene); - } - - //? - //if ((p_path.get_file().get_extension().to_lower() == "glb" || p_path.get_file().get_extension().to_lower() == "gltf") && Math::is_equal_approx(ticks_per_second, 0.0f)) { - // ticks_per_second = 1000.0f; - //} - - if (Math::is_equal_approx(ticks_per_second, 0.0f)) { - ticks_per_second = 25.0f; - } - - Ref<Animation> animation; - animation.instance(); - animation->set_name(name); - animation->set_length(anim->mDuration / ticks_per_second); - - if (name.begins_with("loop") || name.ends_with("loop") || name.begins_with("cycle") || name.ends_with("cycle")) { - animation->set_loop(true); - } - - // generate bone stack for animation import - RegenerateBoneStack(state); - - //regular tracks - for (size_t i = 0; i < anim->mNumChannels; i++) { - const aiNodeAnim *track = anim->mChannels[i]; - String node_name = AssimpUtils::get_assimp_string(track->mNodeName); - print_verbose("track name import: " + node_name); - if (track->mNumRotationKeys == 0 && track->mNumPositionKeys == 0 && track->mNumScalingKeys == 0) { - continue; //do not bother - } - - Skeleton3D *skeleton = nullptr; - NodePath node_path; - aiBone *bone = nullptr; - - // Import skeleton bone animation for this track - // Any bone will do, no point in processing more than just what is in the skeleton - { - bone = get_bone_from_stack(state, track->mNodeName); - - if (bone) { - // get skeleton by bone - skeleton = state.armature_skeletons[bone->mArmature]; - - if (skeleton) { - String path = state.root->get_path_to(skeleton); - path += ":" + node_name; - node_path = path; - - if (node_path != NodePath()) { - _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, - node_path, node_name, bone); - } else { - print_error("Failed to find valid node path for animation"); - } - } - } - } - - // not a bone - // note this is flaky it uses node names which is unreliable - Node *allocated_node = get_node_by_name(state, node_name); - // todo: implement skeleton grabbing for node based animations too :) - // check if node exists, if it does then also apply animation track for node and bones above are all handled. - // this is now inclusive animation handling so that - // we import all the data and do not miss anything. - if (allocated_node) { - node_path = state.root->get_path_to(allocated_node); - - if (node_path != NodePath()) { - _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, - node_path, node_name, nullptr); - } - } - } - - //blend shape tracks - - for (size_t i = 0; i < anim->mNumMorphMeshChannels; i++) { - const aiMeshMorphAnim *anim_mesh = anim->mMorphMeshChannels[i]; - - const String prop_name = AssimpUtils::get_assimp_string(anim_mesh->mName); - const String mesh_name = prop_name.split("*")[0]; - - ERR_CONTINUE(prop_name.split("*").size() != 2); - - Node *item = get_node_by_name(state, mesh_name); - ERR_CONTINUE_MSG(!item, "failed to look up node by name"); - const MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(item); - ERR_CONTINUE(mesh_instance == nullptr); - - String base_path = state.root->get_path_to(mesh_instance); - - Ref<Mesh> mesh = mesh_instance->get_mesh(); - ERR_CONTINUE(mesh.is_null()); - - //add the tracks for this mesh - int base_track = animation->get_track_count(); - for (int j = 0; j < mesh->get_blend_shape_count(); j++) { - animation->add_track(Animation::TYPE_VALUE); - animation->track_set_path(base_track + j, base_path + ":blend_shapes/" + mesh->get_blend_shape_name(j)); - } - - for (size_t k = 0; k < anim_mesh->mNumKeys; k++) { - for (size_t j = 0; j < anim_mesh->mKeys[k].mNumValuesAndWeights; j++) { - float t = anim_mesh->mKeys[k].mTime / ticks_per_second; - float w = anim_mesh->mKeys[k].mWeights[j]; - - animation->track_insert_key(base_track + j, t, w); - } - } - } - - if (animation->get_track_count()) { - state.animation_player->add_animation(name, animation); - } -} - -// -// Mesh Generation from indices ? why do we need so much mesh code -// [debt needs looked into] -Ref<Mesh> -EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, - const aiNode *assimp_node, Ref<Skin> &skin, - Skeleton3D *&skeleton_assigned) { - Ref<ArrayMesh> mesh; - mesh.instance(); - bool has_uvs = false; - uint32_t mesh_flags = 0; - - Map<String, uint32_t> morph_mesh_string_lookup; - - for (int i = 0; i < p_surface_indices.size(); i++) { - const unsigned int mesh_idx = p_surface_indices[0]; - const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_idx]; - for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) { - String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName); - if (!morph_mesh_string_lookup.has(ai_anim_mesh_name)) { - morph_mesh_string_lookup.insert(ai_anim_mesh_name, j); - mesh->set_blend_shape_mode(ArrayMesh::BLEND_SHAPE_MODE_NORMALIZED); - if (ai_anim_mesh_name.empty()) { - ai_anim_mesh_name = String("morph_") + itos(j); - } - mesh->add_blend_shape(ai_anim_mesh_name); - } - } - } - // - // Process Vertex Weights - // - for (int i = 0; i < p_surface_indices.size(); i++) { - const unsigned int mesh_idx = p_surface_indices[i]; - const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_idx]; - - Map<uint32_t, Vector<BoneInfo>> vertex_weights; - - if (ai_mesh->mNumBones > 0) { - for (size_t b = 0; b < ai_mesh->mNumBones; b++) { - aiBone *bone = ai_mesh->mBones[b]; - - if (!skeleton_assigned) { - print_verbose("Assigned mesh skeleton during mesh creation"); - skeleton_assigned = state.skeleton_bone_map[bone]; - - if (!skin.is_valid()) { - print_verbose("Configured new skin"); - skin.instance(); - } else { - print_verbose("Reusing existing skin!"); - } - } - // skeleton_assigned = - String bone_name = AssimpUtils::get_assimp_string(bone->mName); - int bone_index = skeleton_assigned->find_bone(bone_name); - ERR_CONTINUE(bone_index == -1); - for (size_t w = 0; w < bone->mNumWeights; w++) { - aiVertexWeight ai_weights = bone->mWeights[w]; - - BoneInfo bi; - uint32_t vertex_index = ai_weights.mVertexId; - bi.bone = bone_index; - bi.weight = ai_weights.mWeight; - - if (!vertex_weights.has(vertex_index)) { - vertex_weights[vertex_index] = Vector<BoneInfo>(); - } - - vertex_weights[vertex_index].push_back(bi); - } - } - } - - // - // Create mesh from data from assimp - // - - Ref<SurfaceTool> st; - st.instance(); - st->begin(Mesh::PRIMITIVE_TRIANGLES); - - for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { - // Get the texture coordinates if they exist - if (ai_mesh->HasTextureCoords(0)) { - has_uvs = true; - st->set_uv(Vector2(ai_mesh->mTextureCoords[0][j].x, 1.0f - ai_mesh->mTextureCoords[0][j].y)); - } - - if (ai_mesh->HasTextureCoords(1)) { - has_uvs = true; - st->set_uv2(Vector2(ai_mesh->mTextureCoords[1][j].x, 1.0f - ai_mesh->mTextureCoords[1][j].y)); - } - - // Assign vertex colors - if (ai_mesh->HasVertexColors(0)) { - Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b, - ai_mesh->mColors[0]->a); - st->set_color(color); - } - - // Work out normal calculations? - this needs work it doesn't work properly on huestos - if (ai_mesh->mNormals != nullptr) { - const aiVector3D normals = ai_mesh->mNormals[j]; - const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); - st->set_normal(godot_normal); - if (ai_mesh->HasTangentsAndBitangents()) { - const aiVector3D tangents = ai_mesh->mTangents[j]; - const Vector3 godot_tangent = Vector3(tangents.x, tangents.y, tangents.z); - const aiVector3D bitangent = ai_mesh->mBitangents[j]; - const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z); - float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f; - st->set_tangent(Plane(tangents.x, tangents.y, tangents.z, d)); - } - } - - // We have vertex weights right? - if (vertex_weights.has(j)) { - Vector<BoneInfo> bone_info = vertex_weights[j]; - Vector<int> bones; - bones.resize(bone_info.size()); - Vector<float> weights; - weights.resize(bone_info.size()); - - // todo? do we really need to loop over all bones? - assimp may have helper to find all influences on this vertex. - for (int k = 0; k < bone_info.size(); k++) { - bones.write[k] = bone_info[k].bone; - weights.write[k] = bone_info[k].weight; - } - - st->set_bones(bones); - st->set_weights(weights); - } - - // Assign vertex - const aiVector3D pos = ai_mesh->mVertices[j]; - - // note we must include node offset transform as this is relative to world space not local space. - Vector3 godot_pos = Vector3(pos.x, pos.y, pos.z); - st->add_vertex(godot_pos); - } - - // fire replacement for face handling - for (size_t j = 0; j < ai_mesh->mNumFaces; j++) { - const aiFace face = ai_mesh->mFaces[j]; - for (unsigned int k = 0; k < face.mNumIndices; k++) { - st->add_index(face.mIndices[k]); - } - } - - if (ai_mesh->HasTangentsAndBitangents() == false && has_uvs) { - st->generate_tangents(); - } - - aiMaterial *ai_material = state.assimp_scene->mMaterials[ai_mesh->mMaterialIndex]; - Ref<StandardMaterial3D> mat; - mat.instance(); - - int32_t mat_two_sided = 0; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) { - if (mat_two_sided > 0) { - mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED); - } else { - mat->set_cull_mode(StandardMaterial3D::CULL_BACK); - } - } - - aiString mat_name; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) { - mat->set_name(AssimpUtils::get_assimp_string(mat_name)); - } - - // Culling handling for meshes - - // cull all back faces - mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED); - - // Now process materials - aiTextureType base_color = aiTextureType_BASE_COLOR; - { - String filename, path; - AssimpImageData image_data; - - if (AssimpUtils::GetAssimpTexture(state, ai_material, base_color, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - - // anything transparent must be culled - if (image_data.raw_image->detect_alpha() != Image::ALPHA_NONE) { - mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); - mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED); // since you can see both sides in transparent mode - } - - mat->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, image_data.texture); - } - } - - aiTextureType tex_diffuse = aiTextureType_DIFFUSE; - { - String filename, path; - AssimpImageData image_data; - - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_diffuse, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - - // anything transparent must be culled - if (image_data.raw_image->detect_alpha() != Image::ALPHA_NONE) { - mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); - mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED); // since you can see both sides in transparent mode - } - - mat->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, image_data.texture); - } - - aiColor4D clr_diffuse; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, clr_diffuse)) { - if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) { - mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); - mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED); // since you can see both sides in transparent mode - } - mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a)); - } - } - - aiTextureType tex_normal = aiTextureType_NORMALS; - { - String filename, path; - Ref<ImageTexture> texture; - AssimpImageData image_data; - - // Process texture normal map - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_normal, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - mat->set_feature(StandardMaterial3D::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(StandardMaterial3D::TEXTURE_NORMAL, image_data.texture); - } else { - aiString texture_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, AI_PROPERTIES, texture_path)) { - if (AssimpUtils::CreateAssimpTexture(state, texture_path, filename, path, image_data)) { - mat->set_feature(StandardMaterial3D::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(StandardMaterial3D::TEXTURE_NORMAL, image_data.texture); - } - } - } - } - - aiTextureType tex_normal_camera = aiTextureType_NORMAL_CAMERA; - { - String filename, path; - Ref<ImageTexture> texture; - AssimpImageData image_data; - - // Process texture normal map - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_normal_camera, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - mat->set_feature(StandardMaterial3D::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(StandardMaterial3D::TEXTURE_NORMAL, image_data.texture); - } - } - - aiTextureType tex_emission_color = aiTextureType_EMISSION_COLOR; - { - String filename, path; - Ref<ImageTexture> texture; - AssimpImageData image_data; - - // Process texture normal map - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_emission_color, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - mat->set_feature(StandardMaterial3D::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(StandardMaterial3D::TEXTURE_NORMAL, image_data.texture); - } - } - - aiTextureType tex_metalness = aiTextureType_METALNESS; - { - String filename, path; - Ref<ImageTexture> texture; - AssimpImageData image_data; - - // Process texture normal map - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_metalness, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - mat->set_texture(StandardMaterial3D::TEXTURE_METALLIC, image_data.texture); - } - } - - aiTextureType tex_roughness = aiTextureType_DIFFUSE_ROUGHNESS; - { - String filename, path; - Ref<ImageTexture> texture; - AssimpImageData image_data; - - // Process texture normal map - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_roughness, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - mat->set_texture(StandardMaterial3D::TEXTURE_ROUGHNESS, image_data.texture); - } - } - - aiTextureType tex_emissive = aiTextureType_EMISSIVE; - { - String filename = ""; - String path = ""; - Ref<Image> texture; - AssimpImageData image_data; - - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_emissive, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - mat->set_feature(StandardMaterial3D::FEATURE_EMISSION, true); - mat->set_texture(StandardMaterial3D::TEXTURE_EMISSION, image_data.texture); - } else { - // Process emission textures - aiString texture_emissive_path; - if (AI_SUCCESS == - ai_material->Get(AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE, AI_PROPERTIES, texture_emissive_path)) { - if (AssimpUtils::CreateAssimpTexture(state, texture_emissive_path, filename, path, image_data)) { - mat->set_feature(StandardMaterial3D::FEATURE_EMISSION, true); - mat->set_texture(StandardMaterial3D::TEXTURE_EMISSION, image_data.texture); - } - } else { - float pbr_emission = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_EMISSIVE_FACTOR, AI_NULL, pbr_emission)) { - mat->set_emission(Color(pbr_emission, pbr_emission, pbr_emission, 1.0f)); - } - } - } - } - - aiTextureType tex_specular = aiTextureType_SPECULAR; - { - String filename, path; - Ref<ImageTexture> texture; - AssimpImageData image_data; - - // Process texture normal map - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_specular, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - mat->set_texture(StandardMaterial3D::TEXTURE_METALLIC, image_data.texture); - } - } - - aiTextureType tex_ao_map = aiTextureType_AMBIENT_OCCLUSION; - { - String filename, path; - Ref<ImageTexture> texture; - AssimpImageData image_data; - - // Process texture normal map - if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_ao_map, filename, path, image_data)) { - AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); - mat->set_feature(StandardMaterial3D::FEATURE_AMBIENT_OCCLUSION, true); - mat->set_texture(StandardMaterial3D::TEXTURE_AMBIENT_OCCLUSION, image_data.texture); - } - } - - Array array_mesh = st->commit_to_arrays(); - Array morphs; - morphs.resize(ai_mesh->mNumAnimMeshes); - Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; - - for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) { - String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName); - - if (ai_anim_mesh_name.empty()) { - ai_anim_mesh_name = String("morph_") + itos(j); - } - - Array array_copy; - array_copy.resize(RenderingServer::ARRAY_MAX); - - for (int l = 0; l < RenderingServer::ARRAY_MAX; l++) { - array_copy[l] = array_mesh[l].duplicate(true); - } - - const size_t num_vertices = ai_mesh->mAnimMeshes[j]->mNumVertices; - array_copy[Mesh::ARRAY_INDEX] = Variant(); - if (ai_mesh->mAnimMeshes[j]->HasPositions()) { - PackedVector3Array vertices; - vertices.resize(num_vertices); - for (size_t l = 0; l < num_vertices; l++) { - const aiVector3D ai_pos = ai_mesh->mAnimMeshes[j]->mVertices[l]; - Vector3 position = Vector3(ai_pos.x, ai_pos.y, ai_pos.z); - vertices.ptrw()[l] = position; - } - PackedVector3Array new_vertices = array_copy[RenderingServer::ARRAY_VERTEX].duplicate(true); - ERR_CONTINUE(vertices.size() != new_vertices.size()); - for (int32_t l = 0; l < new_vertices.size(); l++) { - Vector3 *w = new_vertices.ptrw(); - w[l] = vertices[l]; - } - array_copy[RenderingServer::ARRAY_VERTEX] = new_vertices; - } - - int32_t color_set = 0; - if (ai_mesh->mAnimMeshes[j]->HasVertexColors(color_set)) { - PackedColorArray colors; - colors.resize(num_vertices); - for (size_t l = 0; l < num_vertices; l++) { - const aiColor4D ai_color = ai_mesh->mAnimMeshes[j]->mColors[color_set][l]; - Color color = Color(ai_color.r, ai_color.g, ai_color.b, ai_color.a); - colors.ptrw()[l] = color; - } - PackedColorArray new_colors = array_copy[RenderingServer::ARRAY_COLOR].duplicate(true); - ERR_CONTINUE(colors.size() != new_colors.size()); - for (int32_t l = 0; l < colors.size(); l++) { - Color *w = new_colors.ptrw(); - w[l] = colors[l]; - } - array_copy[RenderingServer::ARRAY_COLOR] = new_colors; - } - - if (ai_mesh->mAnimMeshes[j]->HasNormals()) { - PackedVector3Array normals; - normals.resize(num_vertices); - for (size_t l = 0; l < num_vertices; l++) { - const aiVector3D ai_normal = ai_mesh->mAnimMeshes[j]->mNormals[l]; - Vector3 normal = Vector3(ai_normal.x, ai_normal.y, ai_normal.z); - normals.ptrw()[l] = normal; - } - PackedVector3Array new_normals = array_copy[RenderingServer::ARRAY_NORMAL].duplicate(true); - ERR_CONTINUE(normals.size() != new_normals.size()); - for (int l = 0; l < normals.size(); l++) { - Vector3 *w = new_normals.ptrw(); - w[l] = normals[l]; - } - array_copy[RenderingServer::ARRAY_NORMAL] = new_normals; - } - - if (ai_mesh->mAnimMeshes[j]->HasTangentsAndBitangents()) { - PackedColorArray tangents; - tangents.resize(num_vertices); - Color *w = tangents.ptrw(); - for (size_t l = 0; l < num_vertices; l++) { - AssimpUtils::calc_tangent_from_mesh(ai_mesh, j, l, l, w); - } - PackedFloat32Array new_tangents = array_copy[RenderingServer::ARRAY_TANGENT].duplicate(true); - ERR_CONTINUE(new_tangents.size() != tangents.size() * 4); - for (int32_t l = 0; l < tangents.size(); l++) { - new_tangents.ptrw()[l + 0] = tangents[l].r; - new_tangents.ptrw()[l + 1] = tangents[l].g; - new_tangents.ptrw()[l + 2] = tangents[l].b; - new_tangents.ptrw()[l + 3] = tangents[l].a; - } - array_copy[RenderingServer::ARRAY_TANGENT] = new_tangents; - } - - morphs[j] = array_copy; - } - mesh->add_surface_from_arrays(primitive, array_mesh, morphs, Dictionary(), mesh_flags); - mesh->surface_set_material(i, mat); - mesh->surface_set_name(i, AssimpUtils::get_assimp_string(ai_mesh->mName)); - } - - return mesh; -} - -/** - * Create a new mesh for the node supplied - */ -MeshInstance3D * -EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *active_node, Transform node_transform) { - /* MESH NODE */ - Ref<Mesh> mesh; - Ref<Skin> skin; - // see if we have mesh cache for this. - Vector<int> surface_indices; - - RegenerateBoneStack(state); - - // Configure indices - for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) { - int mesh_index = assimp_node->mMeshes[i]; - // create list of mesh indexes - surface_indices.push_back(mesh_index); - } - - //surface_indices.sort(); - String mesh_key; - for (int i = 0; i < surface_indices.size(); i++) { - if (i > 0) { - mesh_key += ":"; - } - mesh_key += itos(surface_indices[i]); - } - - Skeleton3D *skeleton = nullptr; - aiNode *armature = nullptr; - - if (!state.mesh_cache.has(mesh_key)) { - mesh = _generate_mesh_from_surface_indices(state, surface_indices, assimp_node, skin, skeleton); - state.mesh_cache[mesh_key] = mesh; - } - - MeshInstance3D *mesh_node = memnew(MeshInstance3D); - mesh = state.mesh_cache[mesh_key]; - mesh_node->set_mesh(mesh); - - // if we have a valid skeleton set it up - if (skin.is_valid()) { - for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) { - unsigned int mesh_index = assimp_node->mMeshes[i]; - const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_index]; - - // please remember bone id relative to the skin is NOT the mesh relative index. - // it is the index relative to the skeleton that is why - // we have state.bone_id_map, it allows for duplicate bone id's too :) - // hope this makes sense - - int bind_count = 0; - for (unsigned int boneId = 0; boneId < ai_mesh->mNumBones; ++boneId) { - aiBone *iterBone = ai_mesh->mBones[boneId]; - - // used to reparent mesh to the correct armature later on if assigned. - if (!armature) { - print_verbose("Configured mesh armature, will reparent later to armature"); - armature = iterBone->mArmature; - } - - if (skeleton) { - int id = skeleton->find_bone(AssimpUtils::get_assimp_string(iterBone->mName)); - if (id != -1) { - print_verbose("Set bind bone: mesh: " + itos(mesh_index) + " bone index: " + itos(id)); - Transform t = AssimpUtils::assimp_matrix_transform(iterBone->mOffsetMatrix); - - skin->add_bind(bind_count, t); - skin->set_bind_bone(bind_count, id); - bind_count++; - } - } - } - } - - print_verbose("Finished configuring bind pose for skin mesh"); - } - - // this code parents all meshes with bones to the armature they are for - // GLTF2 specification relies on this and we are enforcing it for FBX. - if (armature && state.flat_node_map[armature]) { - Node *armature_parent = state.flat_node_map[armature]; - print_verbose("Parented mesh " + node_name + " to armature " + armature_parent->get_name()); - // static mesh handling - armature_parent->add_child(mesh_node); - // transform must be identity - mesh_node->set_global_transform(Transform()); - mesh_node->set_name(node_name); - mesh_node->set_owner(state.root); - } else { - // static mesh handling - active_node->add_child(mesh_node); - mesh_node->set_global_transform(node_transform); - mesh_node->set_name(node_name); - mesh_node->set_owner(state.root); - } - - if (skeleton) { - print_verbose("Attempted to set skeleton path!"); - mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton)); - mesh_node->set_skin(skin); - } - - return mesh_node; -} - -/** - * Create a light for the scene - * Automatically caches lights for lookup later - */ -Node3D *EditorSceneImporterAssimp::create_light( - ImportState &state, - const String &node_name, - Transform &look_at_transform) { - Light3D *light = nullptr; - aiLight *assimp_light = state.assimp_scene->mLights[state.light_cache[node_name]]; - ERR_FAIL_COND_V(!assimp_light, nullptr); - - if (assimp_light->mType == aiLightSource_DIRECTIONAL) { - light = memnew(DirectionalLight3D); - } else if (assimp_light->mType == aiLightSource_POINT) { - light = memnew(OmniLight3D); - } else if (assimp_light->mType == aiLightSource_SPOT) { - light = memnew(SpotLight3D); - } - ERR_FAIL_COND_V(light == nullptr, nullptr); - - if (assimp_light->mType != aiLightSource_POINT) { - Vector3 pos = Vector3( - assimp_light->mPosition.x, - assimp_light->mPosition.y, - assimp_light->mPosition.z); - Vector3 look_at = Vector3( - assimp_light->mDirection.y, - assimp_light->mDirection.x, - assimp_light->mDirection.z) - .normalized(); - Vector3 up = Vector3( - assimp_light->mUp.x, - assimp_light->mUp.y, - assimp_light->mUp.z); - - look_at_transform.set_look_at(pos, look_at, up); - } - // properties for light variables should be put here. - // not really hugely important yet but we will need them in the future - - light->set_color( - Color(assimp_light->mColorDiffuse.r, assimp_light->mColorDiffuse.g, assimp_light->mColorDiffuse.b)); - - return light; -} - -/** - * Create camera for the scene - */ -Node3D *EditorSceneImporterAssimp::create_camera( - ImportState &state, - const String &node_name, - Transform &look_at_transform) { - aiCamera *camera = state.assimp_scene->mCameras[state.camera_cache[node_name]]; - ERR_FAIL_COND_V(!camera, nullptr); - - Camera3D *camera_node = memnew(Camera3D); - ERR_FAIL_COND_V(!camera_node, nullptr); - float near = camera->mClipPlaneNear; - if (Math::is_equal_approx(near, 0.0f)) { - near = 0.1f; - } - camera_node->set_perspective(Math::rad2deg(camera->mHorizontalFOV) * 2.0f, near, camera->mClipPlaneFar); - Vector3 pos = Vector3(camera->mPosition.x, camera->mPosition.y, camera->mPosition.z); - Vector3 look_at = Vector3(camera->mLookAt.y, camera->mLookAt.x, camera->mLookAt.z).normalized(); - Vector3 up = Vector3(camera->mUp.x, camera->mUp.y, camera->mUp.z); - - look_at_transform.set_look_at(pos + look_at_transform.origin, look_at, up); - return camera_node; -} - -/** - * Generate node - * Recursive call to iterate over all nodes - */ -void EditorSceneImporterAssimp::_generate_node( - ImportState &state, - const aiNode *assimp_node) { - ERR_FAIL_COND(assimp_node == nullptr); - state.nodes.push_back(assimp_node); - String parent_name = AssimpUtils::get_assimp_string(assimp_node->mParent->mName); - - // please note - // duplicate bone names exist - // this is why we only check if the bone exists - // so everything else is useless but the name - // please do not copy any other values from get_bone_by_name. - aiBone *parent_bone = get_bone_by_name(state.assimp_scene, assimp_node->mParent->mName); - aiBone *current_bone = get_bone_by_name(state.assimp_scene, assimp_node->mName); - - // is this an armature - // parent null - // and this is the first bone :) - if (parent_bone == nullptr && current_bone) { - state.armature_nodes.push_back(assimp_node->mParent); - print_verbose("found valid armature: " + parent_name); - } - - for (size_t i = 0; i < assimp_node->mNumChildren; i++) { - _generate_node(state, assimp_node->mChildren[i]); - } -} diff --git a/modules/assimp/godot_update_assimp.sh b/modules/assimp/godot_update_assimp.sh deleted file mode 100755 index ff8ff59e97..0000000000 --- a/modules/assimp/godot_update_assimp.sh +++ /dev/null @@ -1,262 +0,0 @@ -rm -rf ../../thirdparty/assimp -cd ../../thirdparty/ -git clone https://github.com/assimp/assimp.git -cd assimp -rm -rf code/3DSExporter.h -rm -rf code/3DSLoader.h -rm -rf code/3MFXmlTags.h -rm -rf code/ABCImporter.h -rm -rf code/ACLoader.h -rm -rf code/AMFImporter_Macro.hpp -rm -rf code/ASELoader.h -rm -rf code/assbin_chunks.h -rm -rf code/AssbinExporter.h -rm -rf code/AssbinLoader.h -rm -rf code/AssimpCExport.cpp -rm -rf code/AssxmlExporter.h -rm -rf code/B3DImporter.h -# rm -rf code/BaseProcess.cpp -# rm -rf code/BaseProcess.h -# rm -rf code/Bitmap.cpp -rm -rf code/BlenderBMesh.cpp -rm -rf code/BlenderBMesh.h -rm -rf code/BlenderCustomData.cpp -rm -rf code/BlenderCustomData.h -rm -rf code/BlenderIntermediate.h -rm -rf code/BlenderLoader.h -rm -rf code/BlenderModifier.h -rm -rf code/BlenderSceneGen.h -rm -rf code/BlenderTessellator.h -rm -rf code/BVHLoader.h -rm -rf code/C4DImporter.h -# rm -rf code/CalcTangentsProcess.h -# rm -rf code/CInterfaceIOWrapper.cpp -# rm -rf code/CInterfaceIOWrapper.h -rm -rf code/COBLoader.h -rm -rf code/COBScene.h -rm -rf code/ColladaExporter.h -rm -rf code/ColladaLoader.h -# rm -rf code/ComputeUVMappingProcess.h -# rm -rf code/ConvertToLHProcess.h -# rm -rf code/CreateAnimMesh.cpp -rm -rf code/CSMLoader.h -rm -rf code/D3MFExporter.h -rm -rf code/D3MFImporter.h -rm -rf code/D3MFOpcPackage.h -# rm -rf code/DeboneProcess.h -# rm -rf code/DefaultIOStream.cpp -# rm -rf code/DefaultIOSystem.cpp -# rm -rf code/DefaultProgressHandler.h -# rm -rf code/DropFaceNormalsProcess.cpp -# rm -rf code/DropFaceNormalsProcess.h -rm -rf code/DXFHelper.h -rm -rf code/DXFLoader.h -# rm -rf code/EmbedTexturesProcess.cpp -# rm -rf code/EmbedTexturesProcess.h -# rm -rf code/FBXCommon.h -# rm -rf code/FBXCompileConfig.h -# rm -rf code/FBXDeformer.cpp -# rm -rf code/FBXDocumentUtil.cpp -# rm -rf code/FBXDocumentUtil.h -# rm -rf code/FBXExporter.h -# rm -rf code/FBXExportNode.h -# rm -rf code/FBXExportProperty.h -# rm -rf code/FBXImporter.cpp -# rm -rf code/FBXImporter.h -# rm -rf code/FBXImportSettings.h -# rm -rf code/FBXMeshGeometry.h -# rm -rf code/FBXModel.cpp -# rm -rf code/FBXNodeAttribute.cpp -# rm -rf code/FBXParser.h -# rm -rf code/FBXProperties.cpp -# rm -rf code/FBXProperties.h -# rm -rf code/FBXTokenizer.cpp -# rm -rf code/FBXTokenizer.h -# rm -rf code/FBXUtil.cpp -# rm -rf code/FBXUtil.h -# rm -rf code/FileLogStream.h -# rm -rf code/FindDegenerates.h -# rm -rf code/FindInstancesProcess.h -# rm -rf code/FindInvalidDataProcess.h -rm -rf code/FIReader.hpp -# rm -rf code/FixNormalsStep.cpp -# rm -rf code/FixNormalsStep.h -# rm -rf code/GenFaceNormalsProcess.cpp -# rm -rf code/GenFaceNormalsProcess.h -# rm -rf code/GenVertexNormalsProcess.cpp -# rm -rf code/GenVertexNormalsProcess.h -rm -rf code/glTF2Asset.h -rm -rf code/glTF2Asset.inl -rm -rf code/glTF2AssetWriter.inl -rm -rf code/glTF2Exporter.cpp -rm -rf code/glTF2Importer.cpp -rm -rf code/glTF2AssetWriter.h -rm -rf code/glTFAsset.h -rm -rf code/glTFAsset.inl -rm -rf code/glTFAssetWriter.inl -rm -rf code/glTFExporter.cpp -rm -rf code/glTFImporter.cpp -rm -rf code/glTF2Exporter.h -rm -rf code/glTF2Importer.h -rm -rf code/glTFAssetWriter.h -rm -rf code/glTFExporter.h -rm -rf code/glTFImporter.h -rm -rf code/HalfLifeFileData.h -rm -rf code/HMPFileData.h -rm -rf code/HMPLoader.h -rm -rf code/HMPLoader.cpp -rm -rf code/IFF.h -# rm -rf code/Importer.h -# rm -rf code/ImproveCacheLocality.h -rm -rf code/IRRLoader.h -rm -rf code/IRRMeshLoader.h -rm -rf code/IRRShared.h -# rm -rf code/JoinVerticesProcess.h -# rm -rf code/LimitBoneWeightsProcess.cpp -# rm -rf code/LimitBoneWeightsProcess.h -rm -rf code/LWSLoader.h -rm -rf code/makefile.mingw -# rm -rf code/MakeVerboseFormat.cpp -# rm -rf code/MakeVerboseFormat.h -# rm -rf code/MaterialSystem.h -rm -rf code/MD2FileData.h -rm -rf code/MD2Loader.h -rm -rf code/MD2NormalTable.h -rm -rf code/MD3FileData.h -rm -rf code/MD3Loader.h -rm -rf code/MD4FileData.h -rm -rf code/MD5Loader.h -rm -rf code/MD5Parser.cpp -rm -rf code/MDCFileData.h -rm -rf code/MDCLoader.h -rm -rf code/MDLDefaultColorMap.h -# rm -rf code/MMDCpp14.h -# rm -rf code/MMDImporter.h -rm -rf code/MS3DLoader.h -rm -rf code/NDOLoader.h -rm -rf code/NFFLoader.h -rm -rf code/ObjExporter.h -rm -rf code/ObjFileImporter.h -rm -rf code/ObjFileMtlImporter.h -rm -rf code/ObjFileParser.h -rm -rf code/ObjTools.h -rm -rf code/ObjExporter.cpp -rm -rf code/ObjFileImporter.cpp -rm -rf code/ObjFileMtlImporter.cpp -rm -rf code/ObjFileParser.cpp -rm -rf code/OFFLoader.h -rm -rf code/OFFLoader.cpp -rm -rf code/OgreImporter.cpp -rm -rf code/OgreImporter.h -rm -rf code/OgreParsingUtils.h -rm -rf code/OgreXmlSerializer.h -rm -rf code/OgreXmlSerializer.cpp -rm -rf code/OgreBinarySerializer.cpp -rm -rf code/OpenGEXExporter.cpp -rm -rf code/OpenGEXExporter.h -rm -rf code/OpenGEXImporter.h -rm -rf code/OpenGEXStructs.h -rm -rf code/OpenGEXImporter.cpp -# rm -rf code/OptimizeGraph.h -# rm -rf code/OptimizeMeshes.cpp -# rm -rf code/OptimizeMeshes.h -rm -rf code/PlyExporter.h -rm -rf code/PlyLoader.h -# rm -rf code/PolyTools.h -# rm -rf code/PostStepRegistry.cpp -# rm -rf code/PretransformVertices.h -rm -rf code/Q3BSPFileData.h -rm -rf code/Q3BSPFileImporter.h -rm -rf code/Q3BSPFileParser.cpp -rm -rf code/Q3BSPFileParser.h -rm -rf code/Q3BSPZipArchive.cpp -rm -rf code/Q3BSPZipArchive.h -rm -rf code/Q3DLoader.h -rm -rf code/Q3DLoader.cpp -rm -rf code/Q3BSPFileImporter.cpp -rm -rf code/RawLoader.h -# rm -rf code/RemoveComments.cpp -# rm -rf code/RemoveRedundantMaterials.cpp -# rm -rf code/RemoveRedundantMaterials.h -# rm -rf code/RemoveVCProcess.h -# rm -rf code/ScaleProcess.cpp -# rm -rf code/ScaleProcess.h -# rm -rf code/scene.cpp -# rm -rf code/ScenePreprocessor.cpp -# rm -rf code/ScenePreprocessor.h -# rm -rf code/ScenePrivate.h -# rm -rf code/SGSpatialSort.cpp -rm -rf code/SIBImporter.h -rm -rf code/SMDLoader.cpp -# rm -rf code/simd.cpp -# rm -rf code/simd.h -# rm -rf code/SortByPTypeProcess.h -# rm -rf code/SplitByBoneCountProcess.h -# rm -rf code/SplitLargeMeshes.h -# rm -rf code/StdOStreamLogStream.h -rm -rf code/StepExporter.h -rm -rf code/StepExporter.cpp -rm -rf code/STLExporter.cpp -rm -rf code/STLExporter.h -rm -rf code/STLLoader.h -rm -rf code/STLLoader.cpp -# rm -rf code/TargetAnimation.cpp -# rm -rf code/TargetAnimation.h -rm -rf code/TerragenLoader.h -rm -rf code/TerragenLoader.cpp -# rm -rf code/TextureTransform.h -# rm -rf code/TriangulateProcess.h -rm -rf code/UnrealLoader.h -# rm -rf code/ValidateDataStructure.h -# rm -rf code/Version.cpp -# rm -rf code/VertexTriangleAdjacency.cpp -# rm -rf code/VertexTriangleAdjacency.h -# rm -rf code/Win32DebugLogStream.h -rm -rf code/X3DImporter_Macro.hpp -rm -rf code/X3DImporter_Metadata.cpp -rm -rf code/X3DImporter_Networking.cpp -rm -rf code/X3DImporter_Texturing.cpp -rm -rf code/X3DImporter_Shape.cpp -rm -rf code/X3DImporter_Rendering.cpp -rm -rf code/X3DImporter_Postprocess.cpp -rm -rf code/X3DImporter_Light.cpp -rm -rf code/X3DImporter_Group.cpp -rm -rf code/X3DImporter_Geometry3D.cpp -rm -rf code/X3DImporter_Geometry2D.cpp -rm -rf code/X3DImporter.cpp -rm -rf code/X3DExporter.cpp -rm -rf code/X3DVocabulary.cpp -rm -rf code/XFileExporter.h -rm -rf code/XFileExporter.cpp -rm -rf code/XFileHelper.h -rm -rf code/XFileHelper.cpp -rm -rf code/XFileImporter.h -rm -rf code/XFileImporter.cpp -rm -rf code/XFileParser.h -rm -rf code/XFileParser.cpp -rm -rf code/XGLLoader.h -rm -rf code/XGLLoader.cpp -rm -rf code/Importer -rm -rf .git -rm -rf cmake-modules -rm -rf doc -rm -rf packaging -rm -rf port -rm -rf samples -rm -rf scripts -rm -rf test -rm -rf tools -rm -rf contrib/zlib -rm -rf contrib/android-cmake -rm -rf contrib/gtest -rm -rf contrib/clipper -rm -rf contrib/irrXML -rm -rf contrib/Open3DGC -rm -rf contrib/openddlparser -rm -rf contrib/poly2tri -#rm -rf contrib/rapidjson -rm -rf contrib/unzip -rm -rf contrib/zip -rm -rf contrib/stb_image -rm .travis* - diff --git a/modules/assimp/import_utils.h b/modules/assimp/import_utils.h deleted file mode 100644 index dc85d06fed..0000000000 --- a/modules/assimp/import_utils.h +++ /dev/null @@ -1,463 +0,0 @@ -/*************************************************************************/ -/* import_utils.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 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 IMPORT_UTILS_IMPORTER_ASSIMP_H -#define IMPORT_UTILS_IMPORTER_ASSIMP_H - -#include "core/io/image_loader.h" -#include "import_state.h" - -#include <assimp/SceneCombiner.h> -#include <assimp/cexport.h> -#include <assimp/cimport.h> -#include <assimp/matrix4x4.h> -#include <assimp/pbrmaterial.h> -#include <assimp/postprocess.h> -#include <assimp/scene.h> -#include <assimp/DefaultLogger.hpp> -#include <assimp/Importer.hpp> -#include <assimp/LogStream.hpp> -#include <assimp/Logger.hpp> -#include <string> - -using namespace AssimpImporter; - -#define AI_PROPERTIES aiTextureType_UNKNOWN, 0 -#define AI_NULL 0, 0 -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR "$raw.Maya|baseColor" -#define AI_MATKEY_FBX_MAYA_METALNESS_FACTOR "$raw.Maya|metalness" -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR "$raw.Maya|diffuseRoughness" - -#define AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE "$raw.Maya|emissionColor|file" -#define AI_MATKEY_FBX_MAYA_EMISSIVE_FACTOR "$raw.Maya|emission" -#define AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE "$raw.Maya|metalness|file" -#define AI_MATKEY_FBX_MAYA_METALNESS_UV_XFORM "$raw.Maya|metalness|uvtrafo" -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE "$raw.Maya|diffuseRoughness|file" -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_UV_XFORM "$raw.Maya|diffuseRoughness|uvtrafo" -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE "$raw.Maya|baseColor|file" -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM "$raw.Maya|baseColor|uvtrafo" -#define AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE "$raw.Maya|normalCamera|file" -#define AI_MATKEY_FBX_MAYA_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo" - -#define AI_MATKEY_FBX_NORMAL_TEXTURE "$raw.Maya|normalCamera|file" -#define AI_MATKEY_FBX_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo" - -#define AI_MATKEY_FBX_MAYA_STINGRAY_DISPLACEMENT_SCALING_FACTOR "$raw.Maya|displacementscaling" -#define AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR "$raw.Maya|base_color" -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR "$raw.Maya|emissive" -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR "$raw.Maya|metallic" -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR "$raw.Maya|roughness" -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR "$raw.Maya|emissive_intensity" - -#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE "$raw.Maya|TEX_normal_map|file" -#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_UV_XFORM "$raw.Maya|TEX_normal_map|uvtrafo" -#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE "$raw.Maya|TEX_color_map|file" -#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM "$raw.Maya|TEX_color_map|uvtrafo" -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE "$raw.Maya|TEX_metallic_map|file" -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_UV_XFORM "$raw.Maya|TEX_metallic_map|uvtrafo" -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE "$raw.Maya|TEX_roughness_map|file" -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_UV_XFORM "$raw.Maya|TEX_roughness_map|uvtrafo" -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE "$raw.Maya|TEX_emissive_map|file" -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_UV_XFORM "$raw.Maya|TEX_emissive_map|uvtrafo" -#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_TEXTURE "$raw.Maya|TEX_ao_map|file" -#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_UV_XFORM "$raw.Maya|TEX_ao_map|uvtrafo" - -/** - * Assimp Utils - * Conversion tools / glue code to convert from assimp to godot -*/ -class AssimpUtils { -public: - /** - * calculate tangents for mesh data from assimp data - */ - static void calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, Color *w) { - const aiVector3D normals = ai_mesh->mAnimMeshes[i]->mNormals[tri_index]; - const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); - const aiVector3D tangent = ai_mesh->mAnimMeshes[i]->mTangents[tri_index]; - const Vector3 godot_tangent = Vector3(tangent.x, tangent.y, tangent.z); - const aiVector3D bitangent = ai_mesh->mAnimMeshes[i]->mBitangents[tri_index]; - const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z); - float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f; - Color plane_tangent = Color(tangent.x, tangent.y, tangent.z, d); - w[index] = plane_tangent; - } - - struct AssetImportFbx { - enum ETimeMode { - TIME_MODE_DEFAULT = 0, - TIME_MODE_120 = 1, - TIME_MODE_100 = 2, - TIME_MODE_60 = 3, - TIME_MODE_50 = 4, - TIME_MODE_48 = 5, - TIME_MODE_30 = 6, - TIME_MODE_30_DROP = 7, - TIME_MODE_NTSC_DROP_FRAME = 8, - TIME_MODE_NTSC_FULL_FRAME = 9, - TIME_MODE_PAL = 10, - TIME_MODE_CINEMA = 11, - TIME_MODE_1000 = 12, - TIME_MODE_CINEMA_ND = 13, - TIME_MODE_CUSTOM = 14, - TIME_MODE_TIME_MODE_COUNT = 15 - }; - enum UpAxis { - UP_VECTOR_AXIS_X = 1, - UP_VECTOR_AXIS_Y = 2, - UP_VECTOR_AXIS_Z = 3 - }; - enum FrontAxis { - FRONT_PARITY_EVEN = 1, - FRONT_PARITY_ODD = 2, - }; - - enum CoordAxis { - COORD_RIGHT = 0, - COORD_LEFT = 1 - }; - }; - - /** Get assimp string - * automatically filters the string data - */ - static String get_assimp_string(const aiString &p_string) { - //convert an assimp String to a Godot String - String name; - name.parse_utf8(p_string.C_Str() /*,p_string.length*/); - if (name.find(":") != -1) { - String replaced_name = name.split(":")[1]; - print_verbose("Replacing " + name + " containing : with " + replaced_name); - name = replaced_name; - } - - return name; - } - - static String get_anim_string_from_assimp(const aiString &p_string) { - String name; - name.parse_utf8(p_string.C_Str() /*,p_string.length*/); - if (name.find(":") != -1) { - String replaced_name = name.split(":")[1]; - print_verbose("Replacing " + name + " containing : with " + replaced_name); - name = replaced_name; - } - return name; - } - - /** - * No filter logic get_raw_string_from_assimp - * This just convers the aiString to a parsed utf8 string - * Without removing special chars etc - */ - static String get_raw_string_from_assimp(const aiString &p_string) { - String name; - name.parse_utf8(p_string.C_Str() /*,p_string.length*/); - return name; - } - - static Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { - return Ref<Animation>(); - } - - /** - * Converts aiMatrix4x4 to godot Transform - */ - static const Transform assimp_matrix_transform(const aiMatrix4x4 p_matrix) { - aiMatrix4x4 matrix = p_matrix; - Transform xform; - xform.set(matrix.a1, matrix.a2, matrix.a3, matrix.b1, matrix.b2, matrix.b3, matrix.c1, matrix.c2, matrix.c3, matrix.a4, matrix.b4, matrix.c4); - return xform; - } - - /** Get fbx fps for time mode meta data - */ - static float get_fbx_fps(int32_t time_mode, const aiScene *p_scene) { - switch (time_mode) { - case AssetImportFbx::TIME_MODE_DEFAULT: - return 24; //hack - case AssetImportFbx::TIME_MODE_120: - return 120; - case AssetImportFbx::TIME_MODE_100: - return 100; - case AssetImportFbx::TIME_MODE_60: - return 60; - case AssetImportFbx::TIME_MODE_50: - return 50; - case AssetImportFbx::TIME_MODE_48: - return 48; - case AssetImportFbx::TIME_MODE_30: - return 30; - case AssetImportFbx::TIME_MODE_30_DROP: - return 30; - case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: - return 29.9700262f; - case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: - return 29.9700262f; - case AssetImportFbx::TIME_MODE_PAL: - return 25; - case AssetImportFbx::TIME_MODE_CINEMA: - return 24; - case AssetImportFbx::TIME_MODE_1000: - return 1000; - case AssetImportFbx::TIME_MODE_CINEMA_ND: - return 23.976f; - case AssetImportFbx::TIME_MODE_CUSTOM: - int32_t frame_rate = -1; - p_scene->mMetaData->Get("FrameRate", frame_rate); - return frame_rate; - } - return 0; - } - - /** - * Get global transform for the current node - so we can use world space rather than - * local space coordinates - * useful if you need global - although recommend using local wherever possible over global - * as you could break fbx scaling :) - */ - static Transform _get_global_assimp_node_transform(const aiNode *p_current_node) { - aiNode const *current_node = p_current_node; - Transform xform; - while (current_node != nullptr) { - xform = assimp_matrix_transform(current_node->mTransformation) * xform; - current_node = current_node->mParent; - } - return xform; - } - - /** - * Find hardcoded textures from assimp which could be in many different directories - */ - static void find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension) { - Vector<String> paths; - paths.push_back(path.get_basename() + extension); - paths.push_back(path + extension); - paths.push_back(path); - paths.push_back(p_path.get_base_dir().plus_file(path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file(path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file(path.get_file())); - paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file())); - paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file())); - paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file())); - paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file())); - paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file())); - paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file())); - paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file())); - paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file().get_basename() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file() + extension)); - paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file())); - for (int i = 0; i < paths.size(); i++) { - if (dir.file_exists(paths[i])) { - found = true; - path = paths[i]; - return; - } - } - } - - /** find the texture path for the supplied fbx path inside godot - * very simple lookup for subfolders etc for a texture which may or may not be in a directory - */ - static void find_texture_path(const String &r_p_path, String &r_path, bool &r_found) { - _Directory dir; - - List<String> exts; - ImageLoader::get_recognized_extensions(&exts); - - Vector<String> split_path = r_path.get_basename().split("*"); - if (split_path.size() == 2) { - r_found = true; - return; - } - - if (dir.file_exists(r_p_path.get_base_dir() + r_path.get_file())) { - r_path = r_p_path.get_base_dir() + r_path.get_file(); - r_found = true; - return; - } - - for (int32_t i = 0; i < exts.size(); i++) { - if (r_found) { - return; - } - find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]); - } - } - - /** - * set_texture_mapping_mode - * Helper to check the mapping mode of the texture (repeat, clamp and mirror) - */ - static void set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<ImageTexture> texture) { - ERR_FAIL_COND(texture.is_null()); - ERR_FAIL_COND(map_mode == nullptr); - // FIXME: Commented out during Vulkan port. - /* - aiTextureMapMode tex_mode = map_mode[0]; - - int32_t flags = Texture2D::FLAGS_DEFAULT; - if (tex_mode == aiTextureMapMode_Wrap) { - //Default - } else if (tex_mode == aiTextureMapMode_Clamp) { - flags = flags & ~Texture2D::FLAG_REPEAT; - } else if (tex_mode == aiTextureMapMode_Mirror) { - flags = flags | Texture2D::FLAG_MIRRORED_REPEAT; - } - texture->set_flags(flags); - */ - } - - /** - * Load or load from cache image :) - */ - static Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path) { - Map<String, Ref<Image>>::Element *match = state.path_to_image_cache.find(p_path); - - // if our cache contains this image then don't bother - if (match) { - return match->get(); - } - - Vector<String> split_path = p_path.get_basename().split("*"); - if (split_path.size() == 2) { - size_t texture_idx = split_path[1].to_int(); - ERR_FAIL_COND_V(texture_idx >= p_scene->mNumTextures, Ref<Image>()); - aiTexture *tex = p_scene->mTextures[texture_idx]; - String filename = AssimpUtils::get_raw_string_from_assimp(tex->mFilename); - filename = filename.get_file(); - print_verbose("Open Asset Import: Loading embedded texture " + filename); - if (tex->mHeight == 0) { - if (tex->CheckFormat("png")) { - ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, Ref<Image>()); - Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); - state.path_to_image_cache.insert(p_path, img); - return img; - } else if (tex->CheckFormat("jpg")) { - ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, Ref<Image>()); - Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); - state.path_to_image_cache.insert(p_path, img); - return img; - } else if (tex->CheckFormat("dds")) { - ERR_FAIL_COND_V_MSG(true, Ref<Image>(), "Open Asset Import: Embedded dds not implemented"); - } - } else { - Ref<Image> img; - img.instance(); - PackedByteArray arr; - uint32_t size = tex->mWidth * tex->mHeight; - arr.resize(size); - memcpy(arr.ptrw(), tex->pcData, size); - ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Image>()); - //ARGB8888 to RGBA8888 - for (int32_t i = 0; i < arr.size() / 4; i++) { - arr.ptrw()[(4 * i) + 3] = arr[(4 * i) + 0]; - arr.ptrw()[(4 * i) + 0] = arr[(4 * i) + 1]; - arr.ptrw()[(4 * i) + 1] = arr[(4 * i) + 2]; - arr.ptrw()[(4 * i) + 2] = arr[(4 * i) + 3]; - } - img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr); - ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); - state.path_to_image_cache.insert(p_path, img); - return img; - } - return Ref<Image>(); - } else { - Ref<Texture2D> texture = ResourceLoader::load(p_path); - ERR_FAIL_COND_V(texture.is_null(), Ref<Image>()); - Ref<Image> image = texture->get_data(); - ERR_FAIL_COND_V(image.is_null(), Ref<Image>()); - state.path_to_image_cache.insert(p_path, image); - return image; - } - - return Ref<Image>(); - } - - /* create texture from assimp data, if found in path */ - static bool CreateAssimpTexture( - AssimpImporter::ImportState &state, - aiString texture_path, - String &filename, - String &path, - AssimpImageData &image_state) { - filename = get_raw_string_from_assimp(texture_path); - path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - find_texture_path(state.path, path, found); - if (found) { - image_state.raw_image = AssimpUtils::load_image(state, state.assimp_scene, path); - if (image_state.raw_image.is_valid()) { - image_state.texture.instance(); - image_state.texture->create_from_image(image_state.raw_image); - // FIXME: Commented out during Vulkan port. - //image_state.texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return true; - } - } - - return false; - } - /** GetAssimpTexture - * Designed to retrieve textures for you - */ - static bool GetAssimpTexture( - AssimpImporter::ImportState &state, - aiMaterial *ai_material, - aiTextureType texture_type, - String &filename, - String &path, - AssimpImageData &image_state) { - aiString ai_filename = aiString(); - if (AI_SUCCESS == ai_material->GetTexture(texture_type, 0, &ai_filename, nullptr, nullptr, nullptr, nullptr, image_state.map_mode)) { - return CreateAssimpTexture(state, ai_filename, filename, path, image_state); - } - - return false; - } -}; - -#endif // IMPORT_UTILS_IMPORTER_ASSIMP_H diff --git a/modules/basis_universal/SCsub b/modules/basis_universal/SCsub index dc7b176d24..351628a0e3 100644 --- a/modules/basis_universal/SCsub +++ b/modules/basis_universal/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_basisu = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + # Not unbundled so far since not widespread as shared library thirdparty_dir = "#thirdparty/basis_universal/" tool_sources = [ @@ -41,8 +44,16 @@ if env["target"] == "debug": env_thirdparty = env_basisu.Clone() env_thirdparty.disable_warnings() if env["tools"]: - env_thirdparty.add_source_files(env.modules_sources, tool_sources) -env_thirdparty.add_source_files(env.modules_sources, transcoder_sources) + env_thirdparty.add_source_files(thirdparty_obj, tool_sources) +env_thirdparty.add_source_files(thirdparty_obj, transcoder_sources) +env.modules_sources += thirdparty_obj # Godot source files -env_basisu.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_basisu.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/bullet/SCsub b/modules/bullet/SCsub index 21bdcca18e..bfac0df5b0 100644 --- a/modules/bullet/SCsub +++ b/modules/bullet/SCsub @@ -7,6 +7,8 @@ env_bullet = env_modules.Clone() # Thirdparty source files +thirdparty_obj = [] + if env["builtin_bullet"]: # Build only version 2 for now (as of 2.89) # Sync file list with relevant upstream CMakeLists.txt for each folder. @@ -208,8 +210,16 @@ if env["builtin_bullet"]: env_thirdparty = env_bullet.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj # Godot source files -env_bullet.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_bullet.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index 663ad6e3e1..3b548b7faa 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -1463,22 +1463,6 @@ bool BulletPhysicsServer3D::generic_6dof_joint_get_flag(RID p_joint, Vector3::Ax return generic_6dof_joint->get_flag(p_axis, p_flag); } -void BulletPhysicsServer3D::generic_6dof_joint_set_precision(RID p_joint, int p_precision) { - JointBullet *joint = joint_owner.getornull(p_joint); - ERR_FAIL_COND(!joint); - ERR_FAIL_COND(joint->get_type() != JOINT_6DOF); - Generic6DOFJointBullet *generic_6dof_joint = static_cast<Generic6DOFJointBullet *>(joint); - generic_6dof_joint->set_precision(p_precision); -} - -int BulletPhysicsServer3D::generic_6dof_joint_get_precision(RID p_joint) { - JointBullet *joint = joint_owner.getornull(p_joint); - ERR_FAIL_COND_V(!joint, 0); - ERR_FAIL_COND_V(joint->get_type() != JOINT_6DOF, 0); - Generic6DOFJointBullet *generic_6dof_joint = static_cast<Generic6DOFJointBullet *>(joint); - return generic_6dof_joint->get_precision(); -} - void BulletPhysicsServer3D::free(RID p_rid) { if (shape_owner.owns(p_rid)) { ShapeBullet *shape = shape_owner.getornull(p_rid); diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h index dca9339c44..07a32e510c 100644 --- a/modules/bullet/bullet_physics_server.h +++ b/modules/bullet/bullet_physics_server.h @@ -376,9 +376,6 @@ public: virtual void generic_6dof_joint_set_flag(RID p_joint, Vector3::Axis p_axis, G6DOFJointAxisFlag p_flag, bool p_enable) override; virtual bool generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis p_axis, G6DOFJointAxisFlag p_flag) override; - virtual void generic_6dof_joint_set_precision(RID p_joint, int precision) override; - virtual int generic_6dof_joint_get_precision(RID p_joint) override; - /* MISC */ virtual void free(RID p_rid) override; diff --git a/modules/bullet/generic_6dof_joint_bullet.cpp b/modules/bullet/generic_6dof_joint_bullet.cpp index 56a66dba45..d75bf1fb98 100644 --- a/modules/bullet/generic_6dof_joint_bullet.cpp +++ b/modules/bullet/generic_6dof_joint_bullet.cpp @@ -273,11 +273,3 @@ bool Generic6DOFJointBullet::get_flag(Vector3::Axis p_axis, PhysicsServer3D::G6D ERR_FAIL_INDEX_V(p_axis, 3, false); return flags[p_axis][p_flag]; } - -void Generic6DOFJointBullet::set_precision(int p_precision) { - sixDOFConstraint->setOverrideNumSolverIterations(MAX(1, p_precision)); -} - -int Generic6DOFJointBullet::get_precision() const { - return sixDOFConstraint->getOverrideNumSolverIterations(); -} diff --git a/modules/bullet/generic_6dof_joint_bullet.h b/modules/bullet/generic_6dof_joint_bullet.h index 316708bb11..ed25337745 100644 --- a/modules/bullet/generic_6dof_joint_bullet.h +++ b/modules/bullet/generic_6dof_joint_bullet.h @@ -68,9 +68,6 @@ public: void set_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag, bool p_value); bool get_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag) const; - - void set_precision(int p_precision); - int get_precision() const; }; #endif diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index 0c64c3640f..284a22717b 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -323,9 +323,6 @@ void RigidBodyBullet::set_space(SpaceBullet *p_space) { can_integrate_forces = false; isScratchedSpaceOverrideModificator = false; - // Remove all eventual constraints - assert_no_constraints(); - // Remove this object form the physics world space->remove_rigid_body(this); } @@ -443,12 +440,6 @@ bool RigidBodyBullet::was_colliding(RigidBodyBullet *p_other_object) { return false; } -void RigidBodyBullet::assert_no_constraints() { - if (btBody->getNumConstraintRefs()) { - WARN_PRINT("A body with a joints is destroyed. Please check the implementation in order to destroy the joint before the body."); - } -} - void RigidBodyBullet::set_activation_state(bool p_active) { if (p_active) { btBody->activate(); diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index c643611397..8ff96577b6 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -267,8 +267,6 @@ public: bool add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, const float &p_appliedImpulse, int p_other_shape_index, int p_local_shape_index); bool was_colliding(RigidBodyBullet *p_other_object); - void assert_no_constraints(); - void set_activation_state(bool p_active); bool is_active() const; diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index abad1beacb..3bfcd83606 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -478,10 +478,20 @@ void SpaceBullet::add_rigid_body(RigidBodyBullet *p_body) { } void SpaceBullet::remove_rigid_body(RigidBodyBullet *p_body) { + btRigidBody *btBody = p_body->get_bt_rigid_body(); + + int constraints = btBody->getNumConstraintRefs(); + if (constraints > 0) { + WARN_PRINT("A body connected to joints was removed. Ensure bodies are disconnected from joints before removing them."); + for (int i = 0; i < constraints; i++) { + dynamicsWorld->removeConstraint(btBody->getConstraintRef(i)); + } + } + if (p_body->is_static()) { - dynamicsWorld->removeCollisionObject(p_body->get_bt_rigid_body()); + dynamicsWorld->removeCollisionObject(btBody); } else { - dynamicsWorld->removeRigidBody(p_body->get_bt_rigid_body()); + dynamicsWorld->removeRigidBody(btBody); } } diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h index e362f27d39..d24a482403 100644 --- a/modules/bullet/space_bullet.h +++ b/modules/bullet/space_bullet.h @@ -167,7 +167,7 @@ public: BulletPhysicsDirectSpaceState *get_direct_state(); void set_debug_contacts(int p_amount) { contactDebug.resize(p_amount); } - _FORCE_INLINE_ bool is_debugging_contacts() const { return !contactDebug.empty(); } + _FORCE_INLINE_ bool is_debugging_contacts() const { return !contactDebug.is_empty(); } _FORCE_INLINE_ void reset_debug_contact_count() { contactDebugCount = 0; } diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 4edca90182..0a20dbd229 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -701,7 +701,7 @@ CSGPrimitive3D::CSGPrimitive3D() { CSGBrush *CSGMesh3D::_build_brush() { if (!mesh.is_valid()) { - return nullptr; + return memnew(CSGBrush); } Vector<Vector3> vertices; @@ -719,7 +719,7 @@ CSGBrush *CSGMesh3D::_build_brush() { if (arrays.size() == 0) { _make_dirty(); - ERR_FAIL_COND_V(arrays.size() == 0, nullptr); + ERR_FAIL_COND_V(arrays.size() == 0, memnew(CSGBrush)); } Vector<Vector3> avertices = arrays[Mesh::ARRAY_VERTEX]; @@ -840,7 +840,7 @@ CSGBrush *CSGMesh3D::_build_brush() { } if (vertices.size() == 0) { - return nullptr; + return memnew(CSGBrush); } return _create_brush_from_arrays(vertices, uvs, smooth, materials); @@ -1465,7 +1465,7 @@ CSGBrush *CSGTorus3D::_build_brush() { float max_radius = outer_radius; if (min_radius == max_radius) { - return nullptr; //sorry, can't + return memnew(CSGBrush); //sorry, can't } if (min_radius > max_radius) { @@ -1684,7 +1684,7 @@ CSGBrush *CSGPolygon3D::_build_brush() { // set our bounding box if (polygon.size() < 3) { - return nullptr; + return memnew(CSGBrush); } Vector<Point2> final_polygon = polygon; @@ -1696,7 +1696,7 @@ CSGBrush *CSGPolygon3D::_build_brush() { Vector<int> triangles = Geometry2D::triangulate_polygon(final_polygon); if (triangles.size() < 3) { - return nullptr; + return memnew(CSGBrush); } Path3D *path = nullptr; @@ -1730,15 +1730,15 @@ CSGBrush *CSGPolygon3D::_build_brush() { if (mode == MODE_PATH) { if (!has_node(path_node)) { - return nullptr; + return memnew(CSGBrush); } Node *n = get_node(path_node); if (!n) { - return nullptr; + return memnew(CSGBrush); } path = Object::cast_to<Path3D>(n); if (!path) { - return nullptr; + return memnew(CSGBrush); } if (path != path_cache) { @@ -1756,10 +1756,10 @@ CSGBrush *CSGPolygon3D::_build_brush() { } curve = path->get_curve(); if (curve.is_null()) { - return nullptr; + return memnew(CSGBrush); } if (curve->get_baked_length() <= 0) { - return nullptr; + return memnew(CSGBrush); } } CSGBrush *brush = memnew(CSGBrush); diff --git a/modules/cvtt/SCsub b/modules/cvtt/SCsub index 5438f7ebac..e56177d6e9 100644 --- a/modules/cvtt/SCsub +++ b/modules/cvtt/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_cvtt = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + thirdparty_dir = "#thirdparty/cvtt/" thirdparty_sources = [ "ConvectionKernels.cpp", @@ -17,7 +20,15 @@ env_cvtt.Prepend(CPPPATH=[thirdparty_dir]) env_thirdparty = env_cvtt.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj # Godot source files -env_cvtt.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_cvtt.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index 5d97164dbf..787b6dbf80 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -171,6 +171,7 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann if (p_channels == Image::USED_CHANNELS_RG) { //guessing this is a normalmap flags |= cvtt::Flags::Uniform; } + options.flags = flags; Image::Format target_format = Image::FORMAT_BPTC_RGBA; diff --git a/modules/denoise/SCsub b/modules/denoise/SCsub index bf3bd7d073..97feea2b44 100644 --- a/modules/denoise/SCsub +++ b/modules/denoise/SCsub @@ -8,6 +8,9 @@ Import("env_modules") env_oidn = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + thirdparty_dir = "#thirdparty/oidn/" thirdparty_sources = [ "core/api.cpp", @@ -106,7 +109,8 @@ env_oidn.Append( env_thirdparty = env_oidn.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza" weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp" @@ -114,5 +118,12 @@ weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp" env_thirdparty.Depends(weights_out_path, weights_in_path) env_thirdparty.CommandNoCache(weights_out_path, weights_in_path, resource_to_cpp.tza_to_cpp) -env_oidn.add_source_files(env.modules_sources, "denoise_wrapper.cpp") -env_modules.add_source_files(env.modules_sources, ["register_types.cpp", "lightmap_denoiser.cpp"]) +# Godot source files + +module_obj = [] + +env_oidn.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/enet/SCsub b/modules/enet/SCsub index c8f4b3885e..580e5a3eb0 100644 --- a/modules/enet/SCsub +++ b/modules/enet/SCsub @@ -7,6 +7,8 @@ env_enet = env_modules.Clone() # Thirdparty source files +thirdparty_obj = [] + if env["builtin_enet"]: thirdparty_dir = "#thirdparty/enet/" thirdparty_sources = [ @@ -26,6 +28,16 @@ if env["builtin_enet"]: env_thirdparty = env_enet.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + + +# Godot source files + +module_obj = [] + +env_enet.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -env_enet.add_source_files(env.modules_sources, "*.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/etc/SCsub b/modules/etc/SCsub index 383bbf83c3..9b46f17916 100644 --- a/modules/etc/SCsub +++ b/modules/etc/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_etc = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + # Not unbundled so far since not widespread as shared library thirdparty_dir = "#thirdparty/etc2comp/" thirdparty_sources = [ @@ -31,7 +34,15 @@ env_etc.Prepend(CPPPATH=[thirdparty_dir]) env_thirdparty = env_etc.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj # Godot source files -env_etc.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_etc.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/etc/image_etc.cpp b/modules/etc/image_compress_etc.cpp index 4e9e64c6a7..552d3025f4 100644 --- a/modules/etc/image_etc.cpp +++ b/modules/etc/image_compress_etc.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* image_etc.cpp */ +/* image_compress_etc.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,16 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "image_etc.h" -#include "Etc.h" -#include "EtcFilter.h" +#include "image_compress_etc.h" + #include "core/io/image.h" #include "core/os/copymem.h" #include "core/os/os.h" #include "core/string/print_string.h" +#include <Etc.h> +#include <EtcFilter.h> + static Image::Format _get_etc2_mode(Image::UsedChannels format) { switch (format) { case Image::USED_CHANNELS_R: @@ -164,12 +166,12 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f int encoding_time = 0; float effort = 0.0; //default, reasonable time - if (p_lossy_quality > 0.75) { - effort = 0.4; + if (p_lossy_quality > 0.95) { + effort = 80; } else if (p_lossy_quality > 0.85) { - effort = 0.6; - } else if (p_lossy_quality > 0.95) { - effort = 0.8; + effort = 60; + } else if (p_lossy_quality > 0.75) { + effort = 40; } Etc::ErrorMetric error_metric = Etc::ErrorMetric::RGBX; // NOTE: we can experiment with other error metrics diff --git a/modules/etc/image_etc.h b/modules/etc/image_compress_etc.h index 7b4f26e127..016e64e4fc 100644 --- a/modules/etc/image_etc.h +++ b/modules/etc/image_compress_etc.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* image_etc.h */ +/* image_compress_etc.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,9 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef IMAGE_ETC1_H -#define IMAGE_ETC1_H +#ifndef IMAGE_COMPRESS_ETC_H +#define IMAGE_COMPRESS_ETC_H void _register_etc_compress_func(); -#endif // IMAGE_ETC_H +#endif // IMAGE_COMPRESS_ETC_H diff --git a/modules/etc/register_types.cpp b/modules/etc/register_types.cpp index 0972857808..225ba6b954 100644 --- a/modules/etc/register_types.cpp +++ b/modules/etc/register_types.cpp @@ -30,7 +30,7 @@ #include "register_types.h" -#include "image_etc.h" +#include "image_compress_etc.h" #include "texture_loader_pkm.h" static Ref<ResourceFormatPKM> resource_loader_pkm; diff --git a/modules/fbx/README.md b/modules/fbx/README.md new file mode 100644 index 0000000000..2a2f186463 --- /dev/null +++ b/modules/fbx/README.md @@ -0,0 +1,196 @@ +# Open Source FBX Specification for the Importer + +The goal of this document is to make everything in FBX clearly stated, any errors will be corrected over time this +is a first draft. + +## fbx parser - originally from assimp + +- Folder: /modules/fbx/fbx_parser +- Upstream: assimp +- Original Version: git (308db73d0b3c2d1870cd3e465eaa283692a4cf23, 2019) +- License: BSD-3-Clause + +This can never be updated from upstream, we have heavily modified the parser to provide memory safety and add some +functionality. If anything we should give this parser back to assimp at some point as it has a lot of new features. + +# Updating assimp fbx parser + +Don't. it's not possible the code is rewritten in many areas to remove thirdparty deps and various bugs are fixed. + +Many days were put into rewriting the parser to use safe code and safe memory accessors. + +# File Headers + +FBX Binaries start with the header "Kaydara FBX Binary" + +FBX ASCII documents contain a larger header, sometimes with copyright information for a file. + +Detecting these is pretty simple. + +# What is an OP link? +It's an object to property link. It lists the properties for that object in some cases. Source and destination based by +ID. + +# What is a OO link? +Its an object to object link, it contains the ID source and destination ID. + +# FBX Node connections + +Nodes in FBX are connected using OO links, This means Object to Object. + +FBX has a single other kind of link which is Object Property, this is used for Object to Property Links, this can be + extra attributes, defaults, or even some simple settings. + +# Bones / Joints / Locators + +Bones in FBX are nodes, they initially have the Model:: Type, then have links to SubDeformer the sub deformer +is part of the skin there is also an explicit Skin link, which then links to the geometry using OO links in the +document. + +# Rotation Order in FBX compared to Godot + +**Godot uses the rotation order:** YXZ + +**FBX has dynamic rotation order to prevent gimbal lock with complex animations** + +```cpp +enum RotOrder { + RotOrder_EulerXYZ = 0 + RotOrder_EulerXZY, + RotOrder_EulerYZX, + RotOrder_EulerYXZ, + RotOrder_EulerZXY, + RotOrder_EulerZYX, + RotOrder_SphericXYZ // nobody uses this - as far as we can tell +}; +``` + + +# Pivot transforms + +### Pivot description: +- Maya and 3DS max consider everything to be in node space (bones joints, skins, lights, cameras, etc) +- Everything is a node, this means essentially nodes are auto or variants +- They are local to the node in the tree. +- They are used to calculate where a node is in space +```c++ +// For a better reference you can check editor_scene_importer_fbx.h +// references: GenFBXTransform / read the data in +// references: ComputePivotTransform / run the calculation +// This is the local pivot transform for the node, not the global transforms +Transform ComputePivotTransform( + Transform chain[TransformationComp_MAXIMUM], + Transform &geometric_transform) { + // Maya pivots + Transform T = chain[TransformationComp_Translation]; + Transform Roff = chain[TransformationComp_RotationOffset]; + Transform Rp = chain[TransformationComp_RotationPivot]; + Transform Rpre = chain[TransformationComp_PreRotation]; + Transform R = chain[TransformationComp_Rotation]; + Transform Rpost = chain[TransformationComp_PostRotation]; + Transform Soff = chain[TransformationComp_ScalingOffset]; + Transform Sp = chain[TransformationComp_ScalingPivot]; + Transform S = chain[TransformationComp_Scaling]; + + // 3DS Max Pivots + Transform OT = chain[TransformationComp_GeometricTranslation]; + Transform OR = chain[TransformationComp_GeometricRotation]; + Transform OS = chain[TransformationComp_GeometricScaling]; + + // Calculate 3DS max pivot transform - use geometric space (e.g doesn't effect children nodes only the current node) + geometric_transform = OT * OR * OS; + // Calculate standard maya pivots + return T * Roff * Rp * Rpre * R * Rpost.inverse() * Rp.inverse() * Soff * Sp * S * Sp.inverse(); +} +``` + +# Transform inheritance for FBX Nodes + +The goal of below is to explain why they implement this in the first place. + +The use case is to make nodes have an option to override their local scaling or to make scaling influenced by orientation, which i would imagine would be useful for when you need to rotate a node and the child to scale based on the orientation rather than setting on the rotation matrix planes. +```cpp +// not modified the formatting here since this code must remain clear +enum TransformInheritance { + Transform_RrSs = 0, + // Parent Rotation * Local Rotation * Parent Scale * Local Scale -- Parent Rotation Offset * Parent ScalingOffset (Local scaling is offset by rotation of parent node) + Transform_RSrs = 1, // Parent Rotation * Parent Scale * Local Rotation * Local Scale -- Parent * Local (normal mode) + Transform_Rrs = 2, // Parent Rotation * Local Rotation * Local Scale -- Node transform scale is the only relevant component + TransformInheritance_MAX // end-of-enum sentinel +}; + +enum TransformInheritance { + Transform_RrSs = 0, + // Local scaling is offset by rotation of parent node + Transform_RSrs = 1, + // Parent * Local (normal mode) + Transform_Rrs = 2, + // Node transform scale is the only relevant component + TransformInheritance_MAX // end-of-enum sentinel +}; +``` + +# Axis in FBX + +Godot has one format for the declared axis + +AXIS X, AXIS Y, -AXIS Z + +FBX supports any format you can think of. As it has to support Maya and 3DS Max. + +#### FBX File Header +```json +GlobalSettings: { + Version: 1000 + Properties70: { + P: "UpAxis", "int", "Integer", "",1 + P: "UpAxisSign", "int", "Integer", "",1 + P: "FrontAxis", "int", "Integer", "",2 + P: "FrontAxisSign", "int", "Integer", "",1 + P: "CoordAxis", "int", "Integer", "",0 + P: "CoordAxisSign", "int", "Integer", "",1 + P: "OriginalUpAxis", "int", "Integer", "",1 + P: "OriginalUpAxisSign", "int", "Integer", "",1 + P: "UnitScaleFactor", "double", "Number", "",1 + P: "OriginalUnitScaleFactor", "double", "Number", "",1 + P: "AmbientColor", "ColorRGB", "Color", "",0,0,0 + P: "DefaultCamera", "KString", "", "", "Producer Perspective" + P: "TimeMode", "enum", "", "",6 + P: "TimeProtocol", "enum", "", "",2 + P: "SnapOnFrameMode", "enum", "", "",0 + P: "TimeSpanStart", "KTime", "Time", "",0 + P: "TimeSpanStop", "KTime", "Time", "",92372316000 + P: "CustomFrameRate", "double", "Number", "",-1 + P: "TimeMarker", "Compound", "", "" + P: "CurrentTimeMarker", "int", "Integer", "",-1 + } +} +``` + +#### FBX FILE declares axis dynamically using FBX header +Coord is X +Up is Y +Front is Z + +#### GODOT - constant reference point +Coord is X positive, +Y is up positive, +Front is -Z negative + +### Explaining MeshGeometry indexing + +Reference type declared: +- Direct (directly related to the mapping information type) +- IndexToDirect (Map with key value, meaning depends on the MappingInformationType) + +ControlPoint is a vertex +* None The mapping is undetermined. +* ByVertex There will be one mapping coordinate for each surface control point/vertex. + * If you have direct reference type vertices [x] + * If you have IndexToDirect reference type the UV +* ByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part. (Sorted by polygon, referencing vertex) +* ByPolygon There can be only one mapping coordinate for the whole polygon. + * One mapping per polygon polygon x has this normal x + * For each vertex of the polygon then set the normal to x +* ByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements. (Mapping is referencing the edge id) +* AllSame There can be only one mapping coordinate for the whole surface. diff --git a/modules/fbx/SCsub b/modules/fbx/SCsub new file mode 100644 index 0000000000..84220a66fa --- /dev/null +++ b/modules/fbx/SCsub @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_fbx = env_modules.Clone() + +# Make includes relative to the folder path specified here so our includes are clean +env_fbx.Prepend(CPPPATH=["#modules/fbx/"]) + +# Godot's own source files +env_fbx.add_source_files(env.modules_sources, "tools/*.cpp") +env_fbx.add_source_files(env.modules_sources, "data/*.cpp") +env_fbx.add_source_files(env.modules_sources, "fbx_parser/*.cpp") +env_fbx.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/fbx/config.py b/modules/fbx/config.py new file mode 100644 index 0000000000..78929800b3 --- /dev/null +++ b/modules/fbx/config.py @@ -0,0 +1,16 @@ +def can_build(env, platform): + return env["tools"] + + +def configure(env): + pass + + +def get_doc_classes(): + return [ + "EditorSceneImporterFBX", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/fbx/data/fbx_anim_container.h b/modules/fbx/data/fbx_anim_container.h new file mode 100644 index 0000000000..e45dd84d6a --- /dev/null +++ b/modules/fbx/data/fbx_anim_container.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* fbx_anim_container.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 FBX_ANIM_CONTAINER_H +#define FBX_ANIM_CONTAINER_H + +#include "core/math/vector3.h" + +// Generic keyframes 99.99 percent of files will be vector3, except if quat interp is used, or visibility tracks +// FBXTrack is used in a map in the implementation in fbx/editor_scene_importer_fbx.cpp +// to avoid having to rewrite the entire logic I refactored this into the code instead. +// once it works I can rewrite so we can add the fun misc features / small features +struct FBXTrack { + bool has_default = false; + Vector3 default_value; + std::map<int64_t, Vector3> keyframes; +}; + +#endif //MODEL_ABSTRACTION_ANIM_CONTAINER_H diff --git a/modules/fbx/data/fbx_bone.cpp b/modules/fbx/data/fbx_bone.cpp new file mode 100644 index 0000000000..7ccb0b3717 --- /dev/null +++ b/modules/fbx/data/fbx_bone.cpp @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* fbx_bone.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "fbx_bone.h" + +#include "fbx_node.h" +#include "import_state.h" + +Ref<FBXNode> FBXSkinDeformer::get_link(const ImportState &state) const { + print_verbose("bone name: " + bone->bone_name); + + // safe for when deformers must be polyfilled when skin has different count of binds to bones in the scene ;) + if (!cluster) { + return nullptr; + } + + ERR_FAIL_COND_V_MSG(cluster->TargetNode() == nullptr, nullptr, "bone has invalid target node"); + + Ref<FBXNode> link_node; + uint64_t id = cluster->TargetNode()->ID(); + if (state.fbx_target_map.has(id)) { + link_node = state.fbx_target_map[id]; + } else { + print_error("link node not found for " + itos(id)); + } + + // the node in space this is for, like if it's FOR a target. + return link_node; +} diff --git a/modules/fbx/data/fbx_bone.h b/modules/fbx/data/fbx_bone.h new file mode 100644 index 0000000000..5d797da24f --- /dev/null +++ b/modules/fbx/data/fbx_bone.h @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* fbx_bone.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 FBX_BONE_H +#define FBX_BONE_H + +#include "fbx_node.h" +#include "import_state.h" + +#include "fbx_parser/FBXDocument.h" + +struct PivotTransform; + +struct FBXBone : public Reference { + uint64_t parent_bone_id = 0; + uint64_t bone_id = 0; + + bool valid_parent = false; // if the parent bone id is set up. + String bone_name = String(); // bone name + + bool is_root_bone() const { + return !valid_parent; + } + + // Godot specific data + int godot_bone_id = -2; // godot internal bone id assigned after import + + // if a bone / armature is the root then FBX skeleton will contain the bone not any other skeleton. + // this is to support joints by themselves in scenes + bool valid_armature_id = false; + uint64_t armature_id = 0; + + /* link node is the parent bone */ + mutable const FBXDocParser::Geometry *geometry = nullptr; + mutable const FBXDocParser::ModelLimbNode *limb_node = nullptr; + + void set_node(Ref<FBXNode> p_node) { + node = p_node; + } + + // Stores the pivot xform for this bone + + Ref<FBXNode> node = nullptr; + Ref<FBXBone> parent_bone = nullptr; + Ref<FBXSkeleton> fbx_skeleton = nullptr; +}; + +struct FBXSkinDeformer { + FBXSkinDeformer(Ref<FBXBone> p_bone, const FBXDocParser::Cluster *p_cluster) : + cluster(p_cluster), bone(p_bone) {} + ~FBXSkinDeformer() {} + const FBXDocParser::Cluster *cluster; + Ref<FBXBone> bone; + + /* get associate model - the model can be invalid sometimes */ + Ref<FBXBone> get_associate_model() const { + return bone->parent_bone; + } + + Ref<FBXNode> get_link(const ImportState &state) const; +}; + +#endif // FBX_BONE_H diff --git a/modules/fbx/data/fbx_material.cpp b/modules/fbx/data/fbx_material.cpp new file mode 100644 index 0000000000..3da9e4e1ff --- /dev/null +++ b/modules/fbx/data/fbx_material.cpp @@ -0,0 +1,464 @@ +/*************************************************************************/ +/* fbx_material.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "fbx_material.h" +#include "scene/resources/material.h" +#include "scene/resources/texture.h" +#include "tools/validation_tools.h" + +String FBXMaterial::get_material_name() const { + return material_name; +} + +void FBXMaterial::set_imported_material(FBXDocParser::Material *p_material) { + material = p_material; +} + +void FBXMaterial::add_search_string(String p_filename, String p_current_directory, String search_directory, Vector<String> &texture_search_paths) { + if (search_directory.is_empty()) { + texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file(p_filename)); + } else { + texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file(search_directory + "/" + p_filename)); + texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file("../" + search_directory + "/" + p_filename)); + } +} + +String find_file(const String &p_base, const String &p_file_to_find) { + _Directory dir; + dir.open(p_base); + + dir.list_dir_begin(); + String n = dir.get_next(); + while (n != String()) { + if (n == "." || n == "..") { + n = dir.get_next(); + continue; + } + if (dir.current_is_dir()) { + // Don't use `path_to` or the returned path will be wrong. + const String f = find_file(p_base + "/" + n, p_file_to_find); + if (f != "") { + return f; + } + } else if (n == p_file_to_find) { + return p_base + "/" + n; + } + n = dir.get_next(); + } + dir.list_dir_end(); + + return String(); +} + +// fbx will not give us good path information and let's not regex them to fix them +// no relative paths are in fbx generally they have a rel field but it's populated incorrectly by the SDK. +String FBXMaterial::find_texture_path_by_filename(const String p_filename, const String p_current_directory) { + _Directory dir; + Vector<String> paths; + add_search_string(p_filename, p_current_directory, "", paths); + add_search_string(p_filename, p_current_directory, "texture", paths); + add_search_string(p_filename, p_current_directory, "textures", paths); + add_search_string(p_filename, p_current_directory, "Textures", paths); + add_search_string(p_filename, p_current_directory, "materials", paths); + add_search_string(p_filename, p_current_directory, "mats", paths); + add_search_string(p_filename, p_current_directory, "pictures", paths); + add_search_string(p_filename, p_current_directory, "images", paths); + + for (int i = 0; i < paths.size(); i++) { + if (dir.file_exists(paths[i])) { + return paths[i]; + } + } + + // We were not able to find the texture in the common locations, + // try to find it into the project globally. + // The common textures can be stored into one of those folders: + // res://asset + // res://texture + // res://material + // res://mat + // res://image + // res://picture + // + // Note the folders can also be called with custom names, like: + // res://my_assets + // since the keyword `asset` is into the directory name the textures will be + // searched there too. + + dir.open("res://"); + dir.list_dir_begin(); + String n = dir.get_next(); + while (n != String()) { + if (n == "." || n == "..") { + n = dir.get_next(); + continue; + } + if (dir.current_is_dir()) { + const String lower_n = n.to_lower(); + if ( + // Don't need to use plural. + lower_n.find("asset") >= 0 || + lower_n.find("texture") >= 0 || + lower_n.find("material") >= 0 || + lower_n.find("mat") >= 0 || + lower_n.find("image") >= 0 || + lower_n.find("picture") >= 0) { + // Don't use `path_to` or the returned path will be wrong. + const String f = find_file(String("res://") + n, p_filename); + if (f != "") { + return f; + } + } + } + n = dir.get_next(); + } + dir.list_dir_end(); + + return ""; +} + +template <class T> +T extract_from_prop(FBXDocParser::PropertyPtr prop, const T &p_default, const std::string &p_name, const String &p_type) { + ERR_FAIL_COND_V_MSG(prop == nullptr, p_default, "invalid property passed to extractor"); + const FBXDocParser::TypedProperty<T> *val = dynamic_cast<const FBXDocParser::TypedProperty<T> *>(prop); + + ERR_FAIL_COND_V_MSG(val == nullptr, p_default, "The FBX is corrupted, the property `" + String(p_name.c_str()) + "` is a `" + String(typeid(*prop).name()) + "` but should be a " + p_type); + // Make sure to not lost any eventual opacity. + return val->Value(); +} + +Ref<StandardMaterial3D> FBXMaterial::import_material(ImportState &state) { + ERR_FAIL_COND_V(material == nullptr, nullptr); + + const String p_fbx_current_directory = state.path; + + Ref<StandardMaterial3D> spatial_material; + spatial_material.instance(); + + // read the material file + // is material two sided + // read material name + print_verbose("[material] material name: " + ImportUtils::FBXNodeToName(material->Name())); + + material_name = ImportUtils::FBXNodeToName(material->Name()); + + for (const std::pair<std::string, const FBXDocParser::Texture *> iter : material->Textures()) { + const uint64_t texture_id = iter.second->ID(); + const std::string &fbx_mapping_name = iter.first; + const FBXDocParser::Texture *fbx_texture_data = iter.second; + const String absolute_texture_path = iter.second->FileName().c_str(); + const String texture_name = absolute_texture_path.get_file(); + const String file_extension = absolute_texture_path.get_extension().to_upper(); + + const String debug_string = "texture id: " + itos(texture_id) + " texture name: " + String(iter.second->Name().c_str()) + " mapping name: " + String(fbx_mapping_name.c_str()); + // remember errors STILL need this string at the end for when you aren't in verbose debug mode :) they need context for when you're not verbose-ing. + print_verbose(debug_string); + + const String file_extension_uppercase = file_extension.to_upper(); + + if (fbx_transparency_flags.count(fbx_mapping_name) > 0) { + // just enable it later let's make this fine-tuned. + spatial_material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA); + } + + ERR_CONTINUE_MSG(file_extension.is_empty(), "your texture has no file extension so we had to ignore it, let us know if you think this is wrong file an issue on github! " + debug_string); + ERR_CONTINUE_MSG(fbx_texture_map.count(fbx_mapping_name) <= 0, "This material has a texture with mapping name: " + String(fbx_mapping_name.c_str()) + " which is not yet supported by this importer. Consider opening an issue so we can support it."); + ERR_CONTINUE_MSG( + file_extension_uppercase != "PNG" && + file_extension_uppercase != "JPEG" && + file_extension_uppercase != "JPG" && + file_extension_uppercase != "TGA" && + file_extension_uppercase != "WEBP" && + file_extension_uppercase != "DDS", + "The FBX file contains a texture with an unrecognized extension: " + file_extension_uppercase); + + print_verbose("Getting FBX mapping mode for " + String(fbx_mapping_name.c_str())); + // get the texture map type + const StandardMaterial3D::TextureParam mapping_mode = fbx_texture_map.at(fbx_mapping_name); + print_verbose("Set FBX mapping mode to " + get_texture_param_name(mapping_mode)); + + Ref<Texture> texture; + print_verbose("texture mapping name: " + texture_name); + + if (state.cached_image_searches.has(texture_name)) { + texture = state.cached_image_searches[texture_name]; + } else { + String path = find_texture_path_by_filename(texture_name, p_fbx_current_directory); + if (!path.is_empty()) { + Ref<Texture2D> image_texture = ResourceLoader::load(path); + + ERR_CONTINUE(image_texture.is_null()); + + texture = image_texture; + state.cached_image_searches.insert(texture_name, texture); + print_verbose("Created texture from loaded image file."); + + } else if (fbx_texture_data != nullptr && fbx_texture_data->Media() != nullptr && fbx_texture_data->Media()->IsEmbedded()) { + // This is an embedded texture. Extract it. + Ref<Image> image; + //image.instance(); // oooo double instance bug? why make Image::_png_blah call + + const String extension = texture_name.get_extension().to_upper(); + if (extension == "PNG") { + // The stored file is a PNG. + image = Image::_png_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength()); + ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded PNG image load fail."); + + } else if ( + extension == "JPEG" || + extension == "JPG") { + // The stored file is a JPEG. + image = Image::_jpg_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength()); + ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded JPEG image load fail."); + + } else if (extension == "TGA") { + // The stored file is a TGA. + image = Image::_tga_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength()); + ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded TGA image load fail."); + + } else if (extension == "WEBP") { + // The stored file is a WEBP. + image = Image::_webp_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength()); + ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded WEBP image load fail."); + + // } else if (extension == "DDS") { + // // In this moment is not possible to extract a DDS from a buffer, TODO consider add it to godot. See `textureloader_dds.cpp::load(). + // // The stored file is a DDS. + } else { + ERR_CONTINUE_MSG(true, "The embedded image with extension: " + extension + " is not yet supported. Open an issue please."); + } + + Ref<ImageTexture> image_texture; + image_texture.instance(); + image_texture->create_from_image(image); + + texture = image_texture; + + // TODO: this is potentially making something with the same name have a match incorrectly USE FBX ID as Hash. #fuck it later. + state.cached_image_searches[texture_name] = texture; + print_verbose("Created texture from embedded image."); + } else { + ERR_CONTINUE_MSG(true, "The FBX texture, with name: `" + texture_name + "`, is not found into the project nor is stored as embedded file. Make sure to insert the texture as embedded file or into the project, then reimport."); + } + } + + spatial_material->set_texture(mapping_mode, texture); + } + + if (spatial_material.is_valid()) { + spatial_material->set_name(material_name); + } + + /// ALL below is related to properties + for (FBXDocParser::LazyPropertyMap::value_type iter : material->Props()->GetLazyProperties()) { + const std::string name = iter.first; + + if (name.empty()) { + continue; + } + + PropertyDesc desc = PROPERTY_DESC_NOT_FOUND; + if (fbx_properties_desc.count(name) > 0) { + desc = fbx_properties_desc.at(name); + } + + // check if we can ignore this it will be done at the next phase + if (desc == PROPERTY_DESC_NOT_FOUND || desc == PROPERTY_DESC_IGNORE) { + // count the texture mapping references. Skip this one if it's found and we can't look up a property value. + if (fbx_texture_map.count(name) > 0) { + continue; // safe to ignore it's a texture mapping. + } + } + + if (desc == PROPERTY_DESC_IGNORE) { + //WARN_PRINT("[Ignored] The FBX material parameter: `" + String(name.c_str()) + "` is ignored."); + continue; + } else { + print_verbose("FBX Material parameter: " + String(name.c_str())); + + // Check for Diffuse material system / lambert materials / legacy basically + if (name == "Diffuse" && !warning_non_pbr_material) { + ValidationTracker::get_singleton()->add_validation_error(state.path, "Invalid material settings change to Ai Standard Surface shader, mat name: " + material_name.c_escape()); + warning_non_pbr_material = true; + } + } + + // DISABLE when adding support for all weird and wonderful material formats + if (desc == PROPERTY_DESC_NOT_FOUND) { + continue; + } + + ERR_CONTINUE_MSG(desc == PROPERTY_DESC_NOT_FOUND, "The FBX material parameter: `" + String(name.c_str()) + "` was not recognized. Please open an issue so we can add the support to it."); + + const FBXDocParser::PropertyTable *tbl = material->Props(); + FBXDocParser::PropertyPtr prop = tbl->Get(name); + + ERR_CONTINUE_MSG(prop == nullptr, "This file may be corrupted because is not possible to extract the material parameter: " + String(name.c_str())); + + if (spatial_material.is_null()) { + // Done here so if no data no material is created. + spatial_material.instance(); + } + + const FBXDocParser::TypedProperty<real_t> *real_value = dynamic_cast<const FBXDocParser::TypedProperty<real_t> *>(prop); + const FBXDocParser::TypedProperty<Vector3> *vector_value = dynamic_cast<const FBXDocParser::TypedProperty<Vector3> *>(prop); + + if (!real_value && !vector_value) { + //WARN_PRINT("unsupported datatype in property: " + String(name.c_str())); + continue; + } + + if (vector_value && !real_value) { + if (vector_value->Value() == Vector3(0, 0, 0) && !real_value) { + continue; + } + } + + switch (desc) { + case PROPERTY_DESC_ALBEDO_COLOR: { + if (vector_value) { + const Vector3 &color = vector_value->Value(); + // Make sure to not lost any eventual opacity. + if (color != Vector3(0, 0, 0)) { + Color c = Color(); + c[0] = color[0]; + c[1] = color[1]; + c[2] = color[2]; + spatial_material->set_albedo(c); + } + + } else if (real_value) { + print_error("albedo is unsupported format?"); + } + } break; + case PROPERTY_DESC_TRANSPARENT: { + if (real_value) { + const real_t opacity = real_value->Value(); + if (opacity < (1.0 - CMP_EPSILON)) { + Color c = spatial_material->get_albedo(); + c.a = opacity; + spatial_material->set_albedo(c); + + spatial_material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA); + spatial_material->set_depth_draw_mode(BaseMaterial3D::DEPTH_DRAW_OPAQUE_ONLY); + } + } else if (vector_value) { + print_error("unsupported transparent desc type vector!"); + } + } break; + case PROPERTY_DESC_SPECULAR: { + if (real_value) { + print_verbose("specular real value: " + rtos(real_value->Value())); + spatial_material->set_specular(MIN(1.0, real_value->Value())); + } + + if (vector_value) { + print_error("unsupported specular vector value: " + vector_value->Value()); + } + } break; + + case PROPERTY_DESC_SPECULAR_COLOR: { + if (vector_value) { + print_error("unsupported specular color: " + vector_value->Value()); + } + } break; + case PROPERTY_DESC_SHINYNESS: { + if (real_value) { + print_error("unsupported shinyness:" + rtos(real_value->Value())); + } + } break; + case PROPERTY_DESC_METALLIC: { + if (real_value) { + print_verbose("metallic real value: " + rtos(real_value->Value())); + spatial_material->set_metallic(MIN(1.0f, real_value->Value())); + } else { + print_error("unsupported value type for metallic"); + } + } break; + case PROPERTY_DESC_ROUGHNESS: { + if (real_value) { + print_verbose("roughness real value: " + rtos(real_value->Value())); + spatial_material->set_roughness(MIN(1.0f, real_value->Value())); + } else { + print_error("unsupported value type for roughness"); + } + } break; + case PROPERTY_DESC_COAT: { + if (real_value) { + print_verbose("clearcoat real value: " + rtos(real_value->Value())); + spatial_material->set_clearcoat(MIN(1.0f, real_value->Value())); + } else { + print_error("unsupported value type for clearcoat"); + } + } break; + case PROPERTY_DESC_COAT_ROUGHNESS: { + // meaning is that approx equal to zero is disabled not actually zero. ;) + if (real_value && Math::is_equal_approx(real_value->Value(), 0.0f)) { + print_verbose("clearcoat real value: " + rtos(real_value->Value())); + spatial_material->set_clearcoat_gloss(1.0 - real_value->Value()); + } else { + print_error("unsupported value type for clearcoat gloss"); + } + } break; + case PROPERTY_DESC_EMISSIVE: { + if (real_value && Math::is_equal_approx(real_value->Value(), 0.0f)) { + print_verbose("Emissive real value: " + rtos(real_value->Value())); + spatial_material->set_emission_energy(real_value->Value()); + } else if (vector_value && !vector_value->Value().is_equal_approx(Vector3(0, 0, 0))) { + const Vector3 &color = vector_value->Value(); + Color c; + c[0] = color[0]; + c[1] = color[1]; + c[2] = color[2]; + spatial_material->set_emission(c); + } + } break; + case PROPERTY_DESC_EMISSIVE_COLOR: { + if (vector_value && !vector_value->Value().is_equal_approx(Vector3(0, 0, 0))) { + const Vector3 &color = vector_value->Value(); + Color c; + c[0] = color[0]; + c[1] = color[1]; + c[2] = color[2]; + spatial_material->set_emission(c); + } else { + print_error("unsupported value type for emissive color"); + } + } break; + case PROPERTY_DESC_NOT_FOUND: + case PROPERTY_DESC_IGNORE: + break; + default: + break; + } + } + + return spatial_material; +} diff --git a/modules/fbx/data/fbx_material.h b/modules/fbx/data/fbx_material.h new file mode 100644 index 0000000000..08b93ac01b --- /dev/null +++ b/modules/fbx/data/fbx_material.h @@ -0,0 +1,286 @@ +/*************************************************************************/ +/* fbx_material.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 FBX_MATERIAL_H +#define FBX_MATERIAL_H + +#include "tools/import_utils.h" + +#include "core/object/reference.h" +#include "core/string/ustring.h" + +struct FBXMaterial : public Reference { + String material_name = String(); + bool warning_non_pbr_material = false; + FBXDocParser::Material *material = nullptr; + + /* Godot materials + *** Texture Maps: + * Albedo - color, texture + * Metallic - specular, metallic, texture + * Roughness - roughness, texture + * Emission - color, texture + * Normal Map - scale, texture + * Ambient Occlusion - texture + * Refraction - scale, texture + *** Has Settings for: + * UV1 - SCALE, OFFSET + * UV2 - SCALE, OFFSET + *** Flags for + * Transparent + * Cull Mode + */ + + enum class MapMode { + AlbedoM = 0, + MetallicM, + SpecularM, + EmissionM, + RoughnessM, + NormalM, + AmbientOcclusionM, + RefractionM, + ReflectionM, + }; + + /* Returns the string representation of the TextureParam enum */ + static String get_texture_param_name(StandardMaterial3D::TextureParam param) { + switch (param) { + case StandardMaterial3D::TEXTURE_ALBEDO: + return "TEXTURE_ALBEDO"; + case StandardMaterial3D::TEXTURE_METALLIC: + return "TEXTURE_METALLIC"; + case StandardMaterial3D::TEXTURE_ROUGHNESS: + return "TEXTURE_ROUGHNESS"; + case StandardMaterial3D::TEXTURE_EMISSION: + return "TEXTURE_EMISSION"; + case StandardMaterial3D::TEXTURE_NORMAL: + return "TEXTURE_NORMAL"; + case StandardMaterial3D::TEXTURE_RIM: + return "TEXTURE_RIM"; + case StandardMaterial3D::TEXTURE_CLEARCOAT: + return "TEXTURE_CLEARCOAT"; + case StandardMaterial3D::TEXTURE_FLOWMAP: + return "TEXTURE_FLOWMAP"; + case StandardMaterial3D::TEXTURE_AMBIENT_OCCLUSION: + return "TEXTURE_AMBIENT_OCCLUSION"; + // case StandardMaterial3D::TEXTURE_DEPTH: // TODO: work out how to make this function again! + // return "TEXTURE_DEPTH"; + case StandardMaterial3D::TEXTURE_SUBSURFACE_SCATTERING: + return "TEXTURE_SUBSURFACE_SCATTERING"; + // case StandardMaterial3D::TEXTURE_TRANSMISSION: // TODO: work out how to make this function again! + // return "TEXTURE_TRANSMISSION"; + case StandardMaterial3D::TEXTURE_REFRACTION: + return "TEXTURE_REFRACTION"; + case StandardMaterial3D::TEXTURE_DETAIL_MASK: + return "TEXTURE_DETAIL_MASK"; + case StandardMaterial3D::TEXTURE_DETAIL_ALBEDO: + return "TEXTURE_DETAIL_ALBEDO"; + case StandardMaterial3D::TEXTURE_DETAIL_NORMAL: + return "TEXTURE_DETAIL_NORMAL"; + case StandardMaterial3D::TEXTURE_MAX: + return "TEXTURE_MAX"; + default: + return "broken horribly"; + } + }; + + // TODO make this static? + const std::map<std::string, bool> fbx_transparency_flags = { + /* Transparent */ + { "TransparentColor", true }, + { "Maya|opacity", true } + }; + + // TODO make this static? + const std::map<std::string, StandardMaterial3D::TextureParam> fbx_texture_map = { + /* Diffuse */ + { "Maya|base", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, + { "DiffuseColor", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, + { "Maya|DiffuseTexture", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, + { "Maya|baseColor", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, + { "Maya|baseColor|file", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, + { "3dsMax|Parameters|base_color_map", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, + { "Maya|TEX_color_map|file", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, + { "Maya|TEX_color_map", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, + /* Emission */ + { "EmissiveColor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, + { "EmissiveFactor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, + { "Maya|emissionColor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, + { "Maya|emissionColor|file", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, + { "3dsMax|Parameters|emission_map", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, + { "Maya|TEX_emissive_map", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, + { "Maya|TEX_emissive_map|file", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, + /* Metallic */ + { "Maya|metalness", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + { "Maya|metalness|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + { "3dsMax|Parameters|metalness_map", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + { "Maya|TEX_metallic_map", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + { "Maya|TEX_metallic_map|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + + /* Roughness */ + // Arnold Roughness Map + { "Maya|specularRoughness", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS }, + + { "3dsMax|Parameters|roughness_map", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS }, + { "Maya|TEX_roughness_map", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS }, + { "Maya|TEX_roughness_map|file", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS }, + + /* Normal */ + { "NormalMap", StandardMaterial3D::TextureParam::TEXTURE_NORMAL }, + //{ "Bump", Material::TextureParam::TEXTURE_NORMAL }, + //{ "3dsMax|Parameters|bump_map", Material::TextureParam::TEXTURE_NORMAL }, + { "Maya|NormalTexture", StandardMaterial3D::TextureParam::TEXTURE_NORMAL }, + //{ "Maya|normalCamera", Material::TextureParam::TEXTURE_NORMAL }, + //{ "Maya|normalCamera|file", Material::TextureParam::TEXTURE_NORMAL }, + { "Maya|TEX_normal_map", StandardMaterial3D::TextureParam::TEXTURE_NORMAL }, + { "Maya|TEX_normal_map|file", StandardMaterial3D::TextureParam::TEXTURE_NORMAL }, + /* AO */ + { "Maya|TEX_ao_map", StandardMaterial3D::TextureParam::TEXTURE_AMBIENT_OCCLUSION }, + { "Maya|TEX_ao_map|file", StandardMaterial3D::TextureParam::TEXTURE_AMBIENT_OCCLUSION }, + + // TODO: specular workflow conversion + // { "SpecularColor", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + // { "Maya|specularColor", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + // { "Maya|SpecularTexture", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + // { "Maya|SpecularTexture|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, + // { "ShininessExponent", SpatialMaterial::TextureParam::UNSUPPORTED }, + // { "ReflectionFactor", SpatialMaterial::TextureParam::UNSUPPORTED }, + + //{ "TransparentColor",SpatialMaterial::TextureParam::TEXTURE_CHANNEL_ALPHA }, + //{ "TransparencyFactor",SpatialMaterial::TextureParam::TEXTURE_CHANNEL_ALPHA } + + // TODO: diffuse roughness + //{ "Maya|diffuseRoughness", SpatialMaterial::TextureParam::UNSUPPORTED }, + //{ "Maya|diffuseRoughness|file", SpatialMaterial::TextureParam::UNSUPPORTED }, + + }; + + // TODO make this static? + enum PropertyDesc { + PROPERTY_DESC_NOT_FOUND, + PROPERTY_DESC_ALBEDO_COLOR, + PROPERTY_DESC_TRANSPARENT, + PROPERTY_DESC_METALLIC, + PROPERTY_DESC_ROUGHNESS, + PROPERTY_DESC_SPECULAR, + PROPERTY_DESC_SPECULAR_COLOR, + PROPERTY_DESC_SHINYNESS, + PROPERTY_DESC_COAT, + PROPERTY_DESC_COAT_ROUGHNESS, + PROPERTY_DESC_EMISSIVE, + PROPERTY_DESC_EMISSIVE_COLOR, + PROPERTY_DESC_IGNORE + }; + + const std::map<std::string, PropertyDesc> fbx_properties_desc = { + /* Albedo */ + { "DiffuseColor", PROPERTY_DESC_ALBEDO_COLOR }, + { "Maya|baseColor", PROPERTY_DESC_ALBEDO_COLOR }, + + /* Specular */ + { "Maya|specular", PROPERTY_DESC_SPECULAR }, + { "Maya|specularColor", PROPERTY_DESC_SPECULAR_COLOR }, + + /* Specular roughness - arnold roughness map */ + { "Maya|specularRoughness", PROPERTY_DESC_ROUGHNESS }, + + /* Transparent */ + { "Opacity", PROPERTY_DESC_TRANSPARENT }, + { "TransparencyFactor", PROPERTY_DESC_TRANSPARENT }, + { "Maya|opacity", PROPERTY_DESC_TRANSPARENT }, + + /* Metallic */ + { "Shininess", PROPERTY_DESC_METALLIC }, + { "Reflectivity", PROPERTY_DESC_METALLIC }, + { "Maya|metalness", PROPERTY_DESC_METALLIC }, + { "Maya|metallic", PROPERTY_DESC_METALLIC }, + + /* Roughness */ + { "Maya|roughness", PROPERTY_DESC_ROUGHNESS }, + + /* Coat */ + //{ "Maya|coat", PROPERTY_DESC_COAT }, + + /* Coat roughness */ + //{ "Maya|coatRoughness", PROPERTY_DESC_COAT_ROUGHNESS }, + + /* Emissive */ + { "Maya|emission", PROPERTY_DESC_EMISSIVE }, + { "Maya|emissive", PROPERTY_DESC_EMISSIVE }, + + /* Emissive color */ + { "EmissiveColor", PROPERTY_DESC_EMISSIVE_COLOR }, + { "Maya|emissionColor", PROPERTY_DESC_EMISSIVE_COLOR }, + + /* Ignore */ + { "Maya|diffuseRoughness", PROPERTY_DESC_IGNORE }, + { "Maya", PROPERTY_DESC_IGNORE }, + { "Diffuse", PROPERTY_DESC_ALBEDO_COLOR }, + { "Maya|TypeId", PROPERTY_DESC_IGNORE }, + { "Ambient", PROPERTY_DESC_IGNORE }, + { "AmbientColor", PROPERTY_DESC_IGNORE }, + { "ShininessExponent", PROPERTY_DESC_IGNORE }, + { "Specular", PROPERTY_DESC_IGNORE }, + { "SpecularColor", PROPERTY_DESC_IGNORE }, + { "SpecularFactor", PROPERTY_DESC_IGNORE }, + //{ "BumpFactor", PROPERTY_DESC_IGNORE }, + { "Maya|exitToBackground", PROPERTY_DESC_IGNORE }, + { "Maya|indirectDiffuse", PROPERTY_DESC_IGNORE }, + { "Maya|indirectSpecular", PROPERTY_DESC_IGNORE }, + { "Maya|internalReflections", PROPERTY_DESC_IGNORE }, + { "DiffuseFactor", PROPERTY_DESC_IGNORE }, + { "AmbientFactor", PROPERTY_DESC_IGNORE }, + { "ReflectionColor", PROPERTY_DESC_IGNORE }, + { "Emissive", PROPERTY_DESC_IGNORE }, + { "Maya|coatColor", PROPERTY_DESC_IGNORE }, + { "Maya|coatNormal", PROPERTY_DESC_IGNORE }, + { "Maya|coatIOR", PROPERTY_DESC_IGNORE }, + }; + + /* storing the texture properties like color */ + template <class T> + struct TexturePropertyMapping : Reference { + StandardMaterial3D::TextureParam map_mode = StandardMaterial3D::TextureParam::TEXTURE_ALBEDO; + const T property = T(); + }; + + static void add_search_string(String p_filename, String p_current_directory, String search_directory, Vector<String> &texture_search_paths); + + static String find_texture_path_by_filename(const String p_filename, const String p_current_directory); + + String get_material_name() const; + + void set_imported_material(FBXDocParser::Material *p_material); + + Ref<StandardMaterial3D> import_material(ImportState &state); +}; + +#endif // FBX_MATERIAL_H diff --git a/modules/fbx/data/fbx_mesh_data.cpp b/modules/fbx/data/fbx_mesh_data.cpp new file mode 100644 index 0000000000..d1c4b70b5c --- /dev/null +++ b/modules/fbx/data/fbx_mesh_data.cpp @@ -0,0 +1,1461 @@ +/*************************************************************************/ +/* fbx_mesh_data.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "fbx_mesh_data.h" + +#include "core/templates/local_vector.h" +#include "scene/resources/mesh.h" +#include "scene/resources/surface_tool.h" + +#include "thirdparty/misc/triangulator.h" + +template <class T> +T collect_first(const Vector<VertexData<T>> *p_data, T p_fall_back) { + if (p_data->is_empty()) { + return p_fall_back; + } + + return (*p_data)[0].data; +} + +template <class T> +HashMap<int, T> collect_all(const Vector<VertexData<T>> *p_data, HashMap<int, T> p_fall_back) { + if (p_data->is_empty()) { + return p_fall_back; + } + + HashMap<int, T> collection; + for (int i = 0; i < p_data->size(); i += 1) { + const VertexData<T> &vd = (*p_data)[i]; + collection[vd.polygon_index] = vd.data; + } + return collection; +} + +template <class T> +T collect_average(const Vector<VertexData<T>> *p_data, T p_fall_back) { + if (p_data->is_empty()) { + return p_fall_back; + } + + T combined = (*p_data)[0].data; // Make sure the data is always correctly initialized. + print_verbose("size of data: " + itos(p_data->size())); + for (int i = 1; i < p_data->size(); i += 1) { + combined += (*p_data)[i].data; + } + combined = combined / real_t(p_data->size()); + + return combined.normalized(); +} + +HashMap<int, Vector3> collect_normal(const Vector<VertexData<Vector3>> *p_data, HashMap<int, Vector3> p_fall_back) { + if (p_data->is_empty()) { + return p_fall_back; + } + + HashMap<int, Vector3> collection; + for (int i = 0; i < p_data->size(); i += 1) { + const VertexData<Vector3> &vd = (*p_data)[i]; + collection[vd.polygon_index] = vd.data; + } + return collection; +} + +HashMap<int, Vector2> collect_uv(const Vector<VertexData<Vector2>> *p_data, HashMap<int, Vector2> p_fall_back) { + if (p_data->is_empty()) { + return p_fall_back; + } + + HashMap<int, Vector2> collection; + for (int i = 0; i < p_data->size(); i += 1) { + const VertexData<Vector2> &vd = (*p_data)[i]; + collection[vd.polygon_index] = vd.data; + } + return collection; +} + +typedef int Vertex; +typedef int SurfaceId; +typedef int PolygonId; +typedef int DataIndex; + +struct SurfaceData { + Ref<SurfaceTool> surface_tool; + OrderedHashMap<Vertex, int> lookup_table; // proposed fix is to replace lookup_table[vertex_id] to give the position of the vertices_map[int] index. + LocalVector<Vertex> vertices_map; // this must be ordered the same as insertion <-- slow to do find() operation. + Ref<Material> material; + HashMap<PolygonId, Vector<DataIndex>> surface_polygon_vertex; + Array morphs; +}; + +EditorSceneImporterMeshNode3D *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression) { + mesh_geometry = p_mesh_geometry; + // todo: make this just use a uint64_t FBX ID this is a copy of our original materials unfortunately. + const std::vector<const FBXDocParser::Material *> &material_lookup = model->GetMaterials(); + + // TODO: perf hotspot on large files + // this can be a very large copy + std::vector<int> polygon_indices = mesh_geometry->get_polygon_indices(); + std::vector<Vector3> vertices = mesh_geometry->get_vertices(); + + // Phase 1. Parse all FBX data. + HashMap<int, Vector3> normals; + HashMap<int, HashMap<int, Vector3>> normals_raw = extract_per_vertex_data( + vertices.size(), + mesh_geometry->get_edge_map(), + polygon_indices, + mesh_geometry->get_normals(), + &collect_all, + HashMap<int, Vector3>()); + + // List<int> keys; + // normals.get_key_list(&keys); + // + // const std::vector<Assimp::FBX::MeshGeometry::Edge>& edges = mesh_geometry->get_edge_map(); + // for (int index = 0; index < keys.size(); index++) { + // const int key = keys[index]; + // const int v1 = edges[key].vertex_0; + // const int v2 = edges[key].vertex_1; + // const Vector3& n1 = normals.get(v1); + // const Vector3& n2 = normals.get(v2); + // print_verbose("[" + itos(v1) + "] n1: " + n1 + "\n[" + itos(v2) + "] n2: " + n2); + // //print_verbose("[" + itos(key) + "] n1: " + n1 + ", n2: " + n2) ; + // //print_verbose("vindex: " + itos(edges[key].vertex_0) + ", vindex2: " + itos(edges[key].vertex_1)); + // //Vector3 ver1 = vertices[edges[key].vertex_0]; + // //Vector3 ver2 = vertices[edges[key].vertex_1]; + // /*real_t angle1 = Math::rad2deg(n1.angle_to(n2)); + // real_t angle2 = Math::rad2deg(n2.angle_to(n1)); + // print_verbose("angle of normals: " + rtos(angle1) + " angle 2" + rtos(angle2));*/ + // } + + HashMap<int, Vector2> uvs_0; + HashMap<int, HashMap<int, Vector2>> uvs_0_raw = extract_per_vertex_data( + vertices.size(), + mesh_geometry->get_edge_map(), + polygon_indices, + mesh_geometry->get_uv_0(), + &collect_all, + HashMap<int, Vector2>()); + + HashMap<int, Vector2> uvs_1; + HashMap<int, HashMap<int, Vector2>> uvs_1_raw = extract_per_vertex_data( + vertices.size(), + mesh_geometry->get_edge_map(), + polygon_indices, + mesh_geometry->get_uv_1(), + &collect_all, + HashMap<int, Vector2>()); + + HashMap<int, Color> colors; + HashMap<int, HashMap<int, Color>> colors_raw = extract_per_vertex_data( + vertices.size(), + mesh_geometry->get_edge_map(), + polygon_indices, + mesh_geometry->get_colors(), + &collect_all, + HashMap<int, Color>()); + + // TODO what about tangents? + // TODO what about bi-nomials? + // TODO there is other? + + HashMap<int, SurfaceId> polygon_surfaces = extract_per_polygon( + vertices.size(), + polygon_indices, + mesh_geometry->get_material_allocation_id(), + -1); + + HashMap<String, MorphVertexData> morphs; + extract_morphs(mesh_geometry, morphs); + + // TODO please add skinning. + //mesh_id = mesh_geometry->ID(); + + sanitize_vertex_weights(state); + + // Re organize polygon vertices to to correctly take into account strange + // UVs. + reorganize_vertices( + polygon_indices, + vertices, + normals, + uvs_0, + uvs_1, + colors, + morphs, + normals_raw, + colors_raw, + uvs_0_raw, + uvs_1_raw); + + const int color_count = colors.size(); + print_verbose("Vertex color count: " + itos(color_count)); + + // Make sure that from this moment on the mesh_geometry is no used anymore. + // This is a safety step, because the mesh_geometry data are no more valid + // at this point. + + const int vertex_count = vertices.size(); + + print_verbose("Vertex count: " + itos(vertex_count)); + + // The map key is the material allocator id that is also used as surface id. + HashMap<SurfaceId, SurfaceData> surfaces; + + // Phase 2. For each material create a surface tool (So a different mesh). + { + if (polygon_surfaces.is_empty()) { + // No material, just use the default one with index -1. + // Set -1 to all polygons. + const int polygon_count = count_polygons(polygon_indices); + for (int p = 0; p < polygon_count; p += 1) { + polygon_surfaces[p] = -1; + } + } + + // Create the surface now. + for (const int *polygon_id = polygon_surfaces.next(nullptr); polygon_id != nullptr; polygon_id = polygon_surfaces.next(polygon_id)) { + const int surface_id = polygon_surfaces[*polygon_id]; + if (surfaces.has(surface_id) == false) { + SurfaceData sd; + sd.surface_tool.instance(); + sd.surface_tool->begin(Mesh::PRIMITIVE_TRIANGLES); + + if (surface_id < 0) { + // nothing to do + } else if (surface_id < (int)material_lookup.size()) { + const FBXDocParser::Material *mat_mapping = material_lookup.at(surface_id); + const uint64_t mapping_id = mat_mapping->ID(); + if (state.cached_materials.has(mapping_id)) { + sd.material = state.cached_materials[mapping_id]; + } + } else { + WARN_PRINT("out of bounds surface detected, FBX file has corrupt material data"); + } + + surfaces.set(surface_id, sd); + } + } + } + + // Phase 3. Map the vertices relative to each surface, in this way we can + // just insert the vertices that we need per each surface. + { + PolygonId polygon_index = -1; + SurfaceId surface_id = -1; + SurfaceData *surface_data = nullptr; + + for (size_t polygon_vertex = 0; polygon_vertex < polygon_indices.size(); polygon_vertex += 1) { + if (is_start_of_polygon(polygon_indices, polygon_vertex)) { + polygon_index += 1; + ERR_FAIL_COND_V_MSG(polygon_surfaces.has(polygon_index) == false, nullptr, "The FBX file is corrupted, This surface_index is not expected."); + surface_id = polygon_surfaces[polygon_index]; + surface_data = surfaces.getptr(surface_id); + CRASH_COND(surface_data == nullptr); // Can't be null. + } + + const int vertex = get_vertex_from_polygon_vertex(polygon_indices, polygon_vertex); + + // The vertex position in the surface + // Uses a lookup table for speed with large scenes + bool has_polygon_vertex_index = surface_data->lookup_table.has(vertex); + int surface_polygon_vertex_index = -1; + + if (has_polygon_vertex_index) { + surface_polygon_vertex_index = surface_data->lookup_table[vertex]; + } else { + surface_polygon_vertex_index = surface_data->vertices_map.size(); + surface_data->lookup_table[vertex] = surface_polygon_vertex_index; + surface_data->vertices_map.push_back(vertex); + } + + surface_data->surface_polygon_vertex[polygon_index].push_back(surface_polygon_vertex_index); + } + } + + //print_verbose("[debug UV 1] UV1: " + itos(uvs_0.size())); + //print_verbose("[debug UV 2] UV2: " + itos(uvs_1.size())); + + // Phase 4. Per each surface just insert the vertices and add the indices. + for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) { + SurfaceData *surface = surfaces.getptr(*surface_id); + + // Just add the vertices data. + for (unsigned int i = 0; i < surface->vertices_map.size(); i += 1) { + const Vertex vertex = surface->vertices_map[i]; + + // This must be done before add_vertex because the surface tool is + // expecting this before the st->add_vertex() call + add_vertex(state, + surface->surface_tool, + state.scale, + vertex, + vertices, + normals, + uvs_0, + uvs_1, + colors); + } + + // Triangulate the various polygons and add the indices. + for (const PolygonId *polygon_id = surface->surface_polygon_vertex.next(nullptr); polygon_id != nullptr; polygon_id = surface->surface_polygon_vertex.next(polygon_id)) { + const Vector<DataIndex> *indices = surface->surface_polygon_vertex.getptr(*polygon_id); + + triangulate_polygon( + surface->surface_tool, + *indices, + surface->vertices_map, + vertices); + } + } + + // Phase 5. Compose the morphs if any. + for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) { + SurfaceData *surface = surfaces.getptr(*surface_id); + + for (const String *morph_name = morphs.next(nullptr); morph_name != nullptr; morph_name = morphs.next(morph_name)) { + MorphVertexData *morph_data = morphs.getptr(*morph_name); + + // As said by the docs, this is not supposed to be different than + // vertex_count. + CRASH_COND(morph_data->vertices.size() != vertex_count); + CRASH_COND(morph_data->normals.size() != vertex_count); + + Vector3 *vertices_ptr = morph_data->vertices.ptrw(); + Vector3 *normals_ptr = morph_data->normals.ptrw(); + + Ref<SurfaceTool> morph_st; + morph_st.instance(); + morph_st->begin(Mesh::PRIMITIVE_TRIANGLES); + + for (unsigned int vi = 0; vi < surface->vertices_map.size(); vi += 1) { + const Vertex vertex = surface->vertices_map[vi]; + add_vertex( + state, + morph_st, + state.scale, + vertex, + vertices, + normals, + uvs_0, + uvs_1, + colors, + vertices_ptr[vertex], + normals_ptr[vertex]); + } + + morph_st->generate_tangents(); + surface->morphs.push_back(morph_st->commit_to_arrays()); + } + } + + // Phase 6. Compose the mesh and return it. + Ref<EditorSceneImporterMesh> mesh; + mesh.instance(); + + // Add blend shape info. + for (const String *morph_name = morphs.next(nullptr); morph_name != nullptr; morph_name = morphs.next(morph_name)) { + mesh->add_blend_shape(*morph_name); + } + + // TODO always normalized, Why? + mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); + + // Add surfaces. + int in_mesh_surface_id = 0; + for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) { + SurfaceData *surface = surfaces.getptr(*surface_id); + + // you can't generate them without a valid uv map. + if (uvs_0_raw.size() > 0) { + surface->surface_tool->generate_tangents(); + } + + Array mesh_array = surface->surface_tool->commit_to_arrays(); + Array blend_shapes = surface->morphs; + + if (surface->material.is_valid()) { + mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, mesh_array, blend_shapes, Dictionary(), surface->material, surface->material->get_name()); + } else { + mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, mesh_array, blend_shapes); + } + + in_mesh_surface_id += 1; + } + + EditorSceneImporterMeshNode3D *godot_mesh = memnew(EditorSceneImporterMeshNode3D); + godot_mesh->set_mesh(mesh); + return godot_mesh; +} + +void FBXMeshData::sanitize_vertex_weights(const ImportState &state) { + const int max_vertex_influence_count = RS::ARRAY_WEIGHTS_SIZE; + Map<int, int> skeleton_to_skin_bind_id; + // TODO: error's need added + const FBXDocParser::Skin *fbx_skin = mesh_geometry->DeformerSkin(); + + if (fbx_skin == nullptr || fbx_skin->Clusters().size() == 0) { + return; // do nothing + } + + // + // Precalculate the skin cluster mapping + // + + int bind_id = 0; + for (const FBXDocParser::Cluster *cluster : fbx_skin->Clusters()) { + Ref<FBXBone> bone = state.fbx_bone_map[cluster->TargetNode()->ID()]; + skeleton_to_skin_bind_id.insert(bone->godot_bone_id, bind_id); + bind_id++; + } + + for (const Vertex *v = vertex_weights.next(nullptr); v != nullptr; v = vertex_weights.next(v)) { + VertexWeightMapping *vm = vertex_weights.getptr(*v); + ERR_CONTINUE(vm->bones.size() != vm->weights.size()); // No message, already checked. + ERR_CONTINUE(vm->bones_ref.size() != vm->weights.size()); // No message, already checked. + + const int initial_size = vm->weights.size(); + { + // Init bone id + int *bones_ptr = vm->bones.ptrw(); + Ref<FBXBone> *bones_ref_ptr = vm->bones_ref.ptrw(); + + for (int i = 0; i < vm->weights.size(); i += 1) { + // At this point this is not possible because the skeleton is already initialized. + CRASH_COND(bones_ref_ptr[i]->godot_bone_id == -2); + bones_ptr[i] = skeleton_to_skin_bind_id[bones_ref_ptr[i]->godot_bone_id]; + } + + // From this point on the data is no more valid. + vm->bones_ref.clear(); + } + + { + // Sort + real_t *weights_ptr = vm->weights.ptrw(); + int *bones_ptr = vm->bones.ptrw(); + for (int i = 0; i < vm->weights.size(); i += 1) { + for (int x = i + 1; x < vm->weights.size(); x += 1) { + if (weights_ptr[i] < weights_ptr[x]) { + SWAP(weights_ptr[i], weights_ptr[x]); + SWAP(bones_ptr[i], bones_ptr[x]); + } + } + } + } + + { + // Resize + vm->weights.resize(max_vertex_influence_count); + vm->bones.resize(max_vertex_influence_count); + real_t *weights_ptr = vm->weights.ptrw(); + int *bones_ptr = vm->bones.ptrw(); + for (int i = initial_size; i < max_vertex_influence_count; i += 1) { + weights_ptr[i] = 0.0; + bones_ptr[i] = 0; + } + + // Normalize + real_t sum = 0.0; + for (int i = 0; i < max_vertex_influence_count; i += 1) { + sum += weights_ptr[i]; + } + if (sum > 0.0) { + for (int i = 0; i < vm->weights.size(); i += 1) { + weights_ptr[i] = weights_ptr[i] / sum; + } + } + } + } +} + +void FBXMeshData::reorganize_vertices( + // TODO: perf hotspot on insane files + std::vector<int> &r_polygon_indices, + std::vector<Vector3> &r_vertices, + HashMap<int, Vector3> &r_normals, + HashMap<int, Vector2> &r_uv_1, + HashMap<int, Vector2> &r_uv_2, + HashMap<int, Color> &r_color, + HashMap<String, MorphVertexData> &r_morphs, + HashMap<int, HashMap<int, Vector3>> &r_normals_raw, + HashMap<int, HashMap<int, Color>> &r_colors_raw, + HashMap<int, HashMap<int, Vector2>> &r_uv_1_raw, + HashMap<int, HashMap<int, Vector2>> &r_uv_2_raw) { + // Key: OldVertex; Value: [New vertices]; + HashMap<int, Vector<int>> duplicated_vertices; + + PolygonId polygon_index = -1; + for (int pv = 0; pv < (int)r_polygon_indices.size(); pv += 1) { + if (is_start_of_polygon(r_polygon_indices, pv)) { + polygon_index += 1; + } + const Vertex index = get_vertex_from_polygon_vertex(r_polygon_indices, pv); + + bool need_duplication = false; + Vector2 this_vert_poly_uv1 = Vector2(); + Vector2 this_vert_poly_uv2 = Vector2(); + Vector3 this_vert_poly_normal = Vector3(); + Color this_vert_poly_color = Color(); + + // Take the normal and see if we need to duplicate this polygon. + if (r_normals_raw.has(index)) { + const HashMap<PolygonId, Vector3> *nrml_arr = r_normals_raw.getptr(index); + + if (nrml_arr->has(polygon_index)) { + this_vert_poly_normal = nrml_arr->get(polygon_index); + } else if (nrml_arr->has(-1)) { + this_vert_poly_normal = nrml_arr->get(-1); + } else { + print_error("invalid normal detected: " + itos(index) + " polygon index: " + itos(polygon_index)); + for (const PolygonId *pid = nrml_arr->next(nullptr); pid != nullptr; pid = nrml_arr->next(pid)) { + print_verbose("debug contents key: " + itos(*pid)); + + if (nrml_arr->has(*pid)) { + print_verbose("contents valid: " + nrml_arr->get(*pid)); + } + } + } + + // Now, check if we need to duplicate it. + for (const PolygonId *pid = nrml_arr->next(nullptr); pid != nullptr; pid = nrml_arr->next(pid)) { + if (*pid == polygon_index) { + continue; + } + + const Vector3 vert_poly_normal = *nrml_arr->getptr(*pid); + if ((this_vert_poly_normal - vert_poly_normal).length_squared() > CMP_EPSILON) { + // Yes this polygon need duplication. + need_duplication = true; + break; + } + } + } + + // TODO: make me vertex color + // Take the normal and see if we need to duplicate this polygon. + if (r_colors_raw.has(index)) { + const HashMap<PolygonId, Color> *color_arr = r_colors_raw.getptr(index); + + if (color_arr->has(polygon_index)) { + this_vert_poly_color = color_arr->get(polygon_index); + } else if (color_arr->has(-1)) { + this_vert_poly_color = color_arr->get(-1); + } else { + print_error("invalid color detected: " + itos(index) + " polygon index: " + itos(polygon_index)); + for (const PolygonId *pid = color_arr->next(nullptr); pid != nullptr; pid = color_arr->next(pid)) { + print_verbose("debug contents key: " + itos(*pid)); + + if (color_arr->has(*pid)) { + print_verbose("contents valid: " + color_arr->get(*pid)); + } + } + } + + // Now, check if we need to duplicate it. + for (const PolygonId *pid = color_arr->next(nullptr); pid != nullptr; pid = color_arr->next(pid)) { + if (*pid == polygon_index) { + continue; + } + + const Color vert_poly_color = *color_arr->getptr(*pid); + if (!this_vert_poly_color.is_equal_approx(vert_poly_color)) { + // Yes this polygon need duplication. + need_duplication = true; + break; + } + } + } + + // Take the UV1 and UV2 and see if we need to duplicate this polygon. + { + HashMap<int, HashMap<int, Vector2>> *uv_raw = &r_uv_1_raw; + Vector2 *this_vert_poly_uv = &this_vert_poly_uv1; + for (int kk = 0; kk < 2; kk++) { + if (uv_raw->has(index)) { + const HashMap<PolygonId, Vector2> *uvs = uv_raw->getptr(index); + + if (uvs->has(polygon_index)) { + // This Polygon has its own uv. + (*this_vert_poly_uv) = *uvs->getptr(polygon_index); + + // Check if we need to duplicate it. + for (const PolygonId *pid = uvs->next(nullptr); pid != nullptr; pid = uvs->next(pid)) { + if (*pid == polygon_index) { + continue; + } + const Vector2 vert_poly_uv = *uvs->getptr(*pid); + if (((*this_vert_poly_uv) - vert_poly_uv).length_squared() > CMP_EPSILON) { + // Yes this polygon need duplication. + need_duplication = true; + break; + } + } + } else if (uvs->has(-1)) { + // It has the default UV. + (*this_vert_poly_uv) = *uvs->getptr(-1); + } else if (uvs->size() > 0) { + // No uv, this is strange, just take the first and duplicate. + (*this_vert_poly_uv) = *uvs->getptr(*uvs->next(nullptr)); + WARN_PRINT("No UVs for this polygon, while there is no default and some other polygons have it. This FBX file may be corrupted."); + } + } + uv_raw = &r_uv_2_raw; + this_vert_poly_uv = &this_vert_poly_uv2; + } + } + + // If we want to duplicate it, Let's see if we already duplicated this + // vertex. + if (need_duplication) { + if (duplicated_vertices.has(index)) { + Vertex similar_vertex = -1; + // Let's see if one of the new vertices has the same data of this. + const Vector<int> *new_vertices = duplicated_vertices.getptr(index); + for (int j = 0; j < new_vertices->size(); j += 1) { + const Vertex new_vertex = (*new_vertices)[j]; + bool same_uv1 = false; + bool same_uv2 = false; + bool same_normal = false; + bool same_color = false; + + if (r_uv_1.has(new_vertex)) { + if ((this_vert_poly_uv1 - (*r_uv_1.getptr(new_vertex))).length_squared() <= CMP_EPSILON) { + same_uv1 = true; + } + } + + if (r_uv_2.has(new_vertex)) { + if ((this_vert_poly_uv2 - (*r_uv_2.getptr(new_vertex))).length_squared() <= CMP_EPSILON) { + same_uv2 = true; + } + } + + if (r_color.has(new_vertex)) { + if (this_vert_poly_color.is_equal_approx((*r_color.getptr(new_vertex)))) { + same_color = true; + } + } + + if (r_normals.has(new_vertex)) { + if ((this_vert_poly_normal - (*r_normals.getptr(new_vertex))).length_squared() <= CMP_EPSILON) { + same_uv2 = true; + } + } + + if (same_uv1 && same_uv2 && same_normal && same_color) { + similar_vertex = new_vertex; + break; + } + } + + if (similar_vertex != -1) { + // Update polygon. + if (is_end_of_polygon(r_polygon_indices, pv)) { + r_polygon_indices[pv] = ~similar_vertex; + } else { + r_polygon_indices[pv] = similar_vertex; + } + need_duplication = false; + } + } + } + + if (need_duplication) { + const Vertex old_index = index; + const Vertex new_index = r_vertices.size(); + + // Polygon index. + if (is_end_of_polygon(r_polygon_indices, pv)) { + r_polygon_indices[pv] = ~new_index; + } else { + r_polygon_indices[pv] = new_index; + } + + // Vertex position. + r_vertices.push_back(r_vertices[old_index]); + + // Normals + if (r_normals_raw.has(old_index)) { + r_normals.set(new_index, this_vert_poly_normal); + r_normals_raw.getptr(old_index)->erase(polygon_index); + r_normals_raw[new_index][polygon_index] = this_vert_poly_normal; + } + + // Vertex Color + if (r_colors_raw.has(old_index)) { + r_color.set(new_index, this_vert_poly_color); + r_colors_raw.getptr(old_index)->erase(polygon_index); + r_colors_raw[new_index][polygon_index] = this_vert_poly_color; + } + + // UV 0 + if (r_uv_1_raw.has(old_index)) { + r_uv_1.set(new_index, this_vert_poly_uv1); + r_uv_1_raw.getptr(old_index)->erase(polygon_index); + r_uv_1_raw[new_index][polygon_index] = this_vert_poly_uv1; + } + + // UV 1 + if (r_uv_2_raw.has(old_index)) { + r_uv_2.set(new_index, this_vert_poly_uv2); + r_uv_2_raw.getptr(old_index)->erase(polygon_index); + r_uv_2_raw[new_index][polygon_index] = this_vert_poly_uv2; + } + + // Morphs + for (const String *mname = r_morphs.next(nullptr); mname != nullptr; mname = r_morphs.next(mname)) { + MorphVertexData *d = r_morphs.getptr(*mname); + // This can't never happen. + CRASH_COND(d == nullptr); + if (d->vertices.size() > old_index) { + d->vertices.push_back(d->vertices[old_index]); + } + if (d->normals.size() > old_index) { + d->normals.push_back(d->normals[old_index]); + } + } + + if (vertex_weights.has(old_index)) { + vertex_weights.set(new_index, vertex_weights[old_index]); + } + + duplicated_vertices[old_index].push_back(new_index); + } else { + if (r_normals_raw.has(index) && + r_normals.has(index) == false) { + r_normals.set(index, this_vert_poly_normal); + } + + if (r_colors_raw.has(index) && r_color.has(index) == false) { + r_color.set(index, this_vert_poly_color); + } + + if (r_uv_1_raw.has(index) && + r_uv_1.has(index) == false) { + r_uv_1.set(index, this_vert_poly_uv1); + } + + if (r_uv_2_raw.has(index) && + r_uv_2.has(index) == false) { + r_uv_2.set(index, this_vert_poly_uv2); + } + } + } +} + +void FBXMeshData::add_vertex( + const ImportState &state, + Ref<SurfaceTool> p_surface_tool, + real_t p_scale, + Vertex p_vertex, + const std::vector<Vector3> &p_vertices_position, + const HashMap<int, Vector3> &p_normals, + const HashMap<int, Vector2> &p_uvs_0, + const HashMap<int, Vector2> &p_uvs_1, + const HashMap<int, Color> &p_colors, + const Vector3 &p_morph_value, + const Vector3 &p_morph_normal) { + ERR_FAIL_INDEX_MSG(p_vertex, (Vertex)p_vertices_position.size(), "FBX file is corrupted, the position of the vertex can't be retrieved."); + + if (p_normals.has(p_vertex)) { + p_surface_tool->set_normal(p_normals[p_vertex] + p_morph_normal); + } + + if (p_uvs_0.has(p_vertex)) { + //print_verbose("uv1: [" + itos(p_vertex) + "] " + p_uvs_0[p_vertex]); + // Inverts Y UV. + p_surface_tool->set_uv(Vector2(p_uvs_0[p_vertex].x, 1 - p_uvs_0[p_vertex].y)); + } + + if (p_uvs_1.has(p_vertex)) { + //print_verbose("uv2: [" + itos(p_vertex) + "] " + p_uvs_1[p_vertex]); + // Inverts Y UV. + p_surface_tool->set_uv2(Vector2(p_uvs_1[p_vertex].x, 1 - p_uvs_1[p_vertex].y)); + } + + if (p_colors.has(p_vertex)) { + p_surface_tool->set_color(p_colors[p_vertex]); + } + + // TODO what about binormals? + // TODO there is other? + + if (vertex_weights.has(p_vertex)) { + // Let's extract the weight info. + const VertexWeightMapping *vm = vertex_weights.getptr(p_vertex); + const Vector<int> &bones = vm->bones; + + // the bug is that the bone idx is wrong because it is not ref'ing the skin. + + if (bones.size() > RS::ARRAY_WEIGHTS_SIZE) { + print_error("[weight overflow detected]"); + } + + p_surface_tool->set_weights(vm->weights); + // 0 1 2 3 4 5 6 7 < local skeleton / skin for mesh + // 0 1 2 3 4 5 6 7 8 9 10 < actual skeleton with all joints + p_surface_tool->set_bones(bones); + } + + // The surface tool want the vertex position as last thing. + p_surface_tool->add_vertex((p_vertices_position[p_vertex] + p_morph_value) * p_scale); +} + +void FBXMeshData::triangulate_polygon(Ref<SurfaceTool> st, Vector<int> p_polygon_vertex, const Vector<Vertex> p_surface_vertex_map, const std::vector<Vector3> &p_vertices) const { + const int polygon_vertex_count = p_polygon_vertex.size(); + if (polygon_vertex_count == 1) { + // point to triangle + st->add_index(p_polygon_vertex[0]); + st->add_index(p_polygon_vertex[0]); + st->add_index(p_polygon_vertex[0]); + return; + } else if (polygon_vertex_count == 2) { + // line to triangle + st->add_index(p_polygon_vertex[1]); + st->add_index(p_polygon_vertex[1]); + st->add_index(p_polygon_vertex[0]); + return; + } else if (polygon_vertex_count == 3) { + // triangle to triangle + st->add_index(p_polygon_vertex[0]); + st->add_index(p_polygon_vertex[2]); + st->add_index(p_polygon_vertex[1]); + return; + } else if (polygon_vertex_count == 4) { + // quad to triangle - this code is awesome for import times + // it prevents triangles being generated slowly + st->add_index(p_polygon_vertex[0]); + st->add_index(p_polygon_vertex[2]); + st->add_index(p_polygon_vertex[1]); + st->add_index(p_polygon_vertex[2]); + st->add_index(p_polygon_vertex[0]); + st->add_index(p_polygon_vertex[3]); + return; + } else { + // non triangulated - we must run the triangulation algorithm + bool is_simple_convex = false; + // this code is 'slow' but required it triangulates all the unsupported geometry. + // Doesn't allow for bigger polygons because those are unlikely be convex + if (polygon_vertex_count <= 6) { + // Start from true, check if it's false. + is_simple_convex = true; + Vector3 first_vec; + for (int i = 0; i < polygon_vertex_count; i += 1) { + const Vector3 p1 = p_vertices[p_surface_vertex_map[p_polygon_vertex[i]]]; + const Vector3 p2 = p_vertices[p_surface_vertex_map[p_polygon_vertex[(i + 1) % polygon_vertex_count]]]; + const Vector3 p3 = p_vertices[p_surface_vertex_map[p_polygon_vertex[(i + 2) % polygon_vertex_count]]]; + + const Vector3 edge1 = p1 - p2; + const Vector3 edge2 = p3 - p2; + + const Vector3 res = edge1.normalized().cross(edge2.normalized()).normalized(); + if (i == 0) { + first_vec = res; + } else { + if (first_vec.dot(res) < 0.0) { + // Ok we found an angle that is not the same dir of the + // others. + is_simple_convex = false; + break; + } + } + } + } + + if (is_simple_convex) { + // This is a convex polygon, so just triangulate it. + for (int i = 0; i < (polygon_vertex_count - 2); i += 1) { + st->add_index(p_polygon_vertex[2 + i]); + st->add_index(p_polygon_vertex[1 + i]); + st->add_index(p_polygon_vertex[0]); + } + return; + } + } + + { + // This is a concave polygon. + + std::vector<Vector3> poly_vertices(polygon_vertex_count); + for (int i = 0; i < polygon_vertex_count; i += 1) { + poly_vertices[i] = p_vertices[p_surface_vertex_map[p_polygon_vertex[i]]]; + } + + const Vector3 poly_norm = get_poly_normal(poly_vertices); + if (poly_norm.length_squared() <= CMP_EPSILON) { + ERR_FAIL_COND_MSG(poly_norm.length_squared() <= CMP_EPSILON, "The normal of this poly was not computed. Is this FBX file corrupted."); + } + + // Select the plan coordinate. + int axis_1_coord = 0; + int axis_2_coord = 1; + { + real_t inv = poly_norm.z; + + const real_t axis_x = ABS(poly_norm.x); + const real_t axis_y = ABS(poly_norm.y); + const real_t axis_z = ABS(poly_norm.z); + + if (axis_x > axis_y) { + if (axis_x > axis_z) { + // For the most part the normal point toward X. + axis_1_coord = 1; + axis_2_coord = 2; + inv = poly_norm.x; + } + } else if (axis_y > axis_z) { + // For the most part the normal point toward Y. + axis_1_coord = 2; + axis_2_coord = 0; + inv = poly_norm.y; + } + + // Swap projection axes to take the negated projection vector into account + if (inv < 0.0f) { + SWAP(axis_1_coord, axis_2_coord); + } + } + + TriangulatorPoly triangulator_poly; + triangulator_poly.Init(polygon_vertex_count); + std::vector<Vector2> projected_vertices(polygon_vertex_count); + for (int i = 0; i < polygon_vertex_count; i += 1) { + const Vector2 pv(poly_vertices[i][axis_1_coord], poly_vertices[i][axis_2_coord]); + projected_vertices[i] = pv; + triangulator_poly.GetPoint(i) = pv; + } + triangulator_poly.SetOrientation(TRIANGULATOR_CCW); + + List<TriangulatorPoly> out_poly; + + TriangulatorPartition triangulator_partition; + if (triangulator_partition.Triangulate_OPT(&triangulator_poly, &out_poly) == 0) { // Good result. + if (triangulator_partition.Triangulate_EC(&triangulator_poly, &out_poly) == 0) { // Medium result. + if (triangulator_partition.Triangulate_MONO(&triangulator_poly, &out_poly) == 0) { // Really poor result. + ERR_FAIL_MSG("The triangulation of this polygon failed, please try to triangulate your mesh or check if it has broken polygons."); + } + } + } + + std::vector<Vector2> tris(out_poly.size()); + for (List<TriangulatorPoly>::Element *I = out_poly.front(); I; I = I->next()) { + TriangulatorPoly &tp = I->get(); + + ERR_FAIL_COND_MSG(tp.GetNumPoints() != 3, "The triangulator retuned more points, how this is possible?"); + // Find Index + for (int i = 2; i >= 0; i -= 1) { + const Vector2 vertex = tp.GetPoint(i); + bool done = false; + // Find Index + for (int y = 0; y < polygon_vertex_count; y += 1) { + if ((projected_vertices[y] - vertex).length_squared() <= CMP_EPSILON) { + // This seems the right vertex + st->add_index(p_polygon_vertex[y]); + done = true; + break; + } + } + ERR_FAIL_COND(done == false); + } + } + } +} + +void FBXMeshData::gen_weight_info(Ref<SurfaceTool> st, Vertex vertex_id) const { + if (vertex_weights.is_empty()) { + return; + } + + if (vertex_weights.has(vertex_id)) { + // Let's extract the weight info. + const VertexWeightMapping *vm = vertex_weights.getptr(vertex_id); + st->set_weights(vm->weights); + st->set_bones(vm->bones); + } +} + +int FBXMeshData::get_vertex_from_polygon_vertex(const std::vector<int> &p_polygon_indices, int p_index) const { + if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) { + return -1; + } + + const int vertex = p_polygon_indices[p_index]; + if (vertex >= 0) { + return vertex; + } else { + // Negative numbers are the end of the face, reversing the bits is + // possible to obtain the positive correct vertex number. + return ~vertex; + } +} + +bool FBXMeshData::is_end_of_polygon(const std::vector<int> &p_polygon_indices, int p_index) const { + if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) { + return false; + } + + const int vertex = p_polygon_indices[p_index]; + + // If the index is negative this is the end of the Polygon. + return vertex < 0; +} + +bool FBXMeshData::is_start_of_polygon(const std::vector<int> &p_polygon_indices, int p_index) const { + if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) { + return false; + } + + if (p_index == 0) { + return true; + } + + // If the previous indices is negative this is the begin of a new Polygon. + return p_polygon_indices[p_index - 1] < 0; +} + +int FBXMeshData::count_polygons(const std::vector<int> &p_polygon_indices) const { + // The negative numbers define the end of the polygon. Counting the amount of + // negatives the numbers of polygons are obtained. + int count = 0; + for (size_t i = 0; i < p_polygon_indices.size(); i += 1) { + if (p_polygon_indices[i] < 0) { + count += 1; + } + } + return count; +} + +template <class R, class T> +HashMap<int, R> FBXMeshData::extract_per_vertex_data( + int p_vertex_count, + const std::vector<FBXDocParser::MeshGeometry::Edge> &p_edge_map, + const std::vector<int> &p_mesh_indices, + const FBXDocParser::MeshGeometry::MappingData<T> &p_mapping_data, + R (*collector_function)(const Vector<VertexData<T>> *p_vertex_data, R p_fall_back), + R p_fall_back) const { + /* When index_to_direct is set + * index size is 184 ( contains index for the data array [values 0, 96] ) + * data size is 96 (contains uv coordinates) + * this means index is simple data reduction basically + */ + //// + if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_mapping_data.index.size() == 0) { + print_verbose("debug count: index size: " + itos(p_mapping_data.index.size()) + ", data size: " + itos(p_mapping_data.data.size())); + print_verbose("vertex indices count: " + itos(p_mesh_indices.size())); + print_verbose("Edge map size: " + itos(p_edge_map.size())); + } + + ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_mapping_data.index.size() == 0, (HashMap<int, R>()), "FBX importer needs to map correctly to this field, please specify the override index name to fix this problem!"); + ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index && p_mapping_data.index.size() == 0, (HashMap<int, R>()), "The FBX seems corrupted"); + + // Aggregate vertex data. + HashMap<Vertex, Vector<VertexData<T>>> aggregate_vertex_data; + + switch (p_mapping_data.map_type) { + case FBXDocParser::MeshGeometry::MapType::none: { + // No data nothing to do. + return (HashMap<int, R>()); + } + case FBXDocParser::MeshGeometry::MapType::vertex: { + ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct, (HashMap<int, R>()), "We will support in future"); + + if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { + // The data is mapped per vertex directly. + ERR_FAIL_COND_V_MSG((int)p_mapping_data.data.size() != p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR01"); + for (size_t vertex_index = 0; vertex_index < p_mapping_data.data.size(); vertex_index += 1) { + aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[vertex_index] }); + } + } else { + // The data is mapped per vertex using a reference. + // The indices array, contains a *reference_id for each vertex. + // * Note that the reference_id is the id of data into the data array. + // + // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html + ERR_FAIL_COND_V_MSG((int)p_mapping_data.index.size() != p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR02"); + for (size_t vertex_index = 0; vertex_index < p_mapping_data.index.size(); vertex_index += 1) { + ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[vertex_index], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR03."); + aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[p_mapping_data.index[vertex_index]] }); + } + } + } break; + case FBXDocParser::MeshGeometry::MapType::polygon_vertex: { + if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct) { + // The data is mapped using each index from the indexes array then direct to the data (data reduction algorithm) + ERR_FAIL_COND_V_MSG((int)p_mesh_indices.size() != (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR04"); + int polygon_id = -1; + for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.index.size(); polygon_vertex_index += 1) { + if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { + polygon_id += 1; + } + const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); + ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR05"); + ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR06"); + const int index_to_direct = p_mapping_data.index[polygon_vertex_index]; + T value = p_mapping_data.data[index_to_direct]; + aggregate_vertex_data[vertex_index].push_back({ polygon_id, value }); + } + } else if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { + // The data are mapped per polygon vertex directly. + ERR_FAIL_COND_V_MSG((int)p_mesh_indices.size() != (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR04"); + int polygon_id = -1; + for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.data.size(); polygon_vertex_index += 1) { + if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { + polygon_id += 1; + } + const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); + ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR05"); + ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR06"); + + aggregate_vertex_data[vertex_index].push_back({ polygon_id, p_mapping_data.data[polygon_vertex_index] }); + } + } else { + // The data is mapped per polygon_vertex using a reference. + // The indices array, contains a *reference_id for each polygon_vertex. + // * Note that the reference_id is the id of data into the data array. + // + // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html + ERR_FAIL_COND_V_MSG(p_mesh_indices.size() != p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR7"); + int polygon_id = -1; + for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.index.size(); polygon_vertex_index += 1) { + if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { + polygon_id += 1; + } + const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); + ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR8"); + ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR9."); + ERR_FAIL_COND_V_MSG(p_mapping_data.index[polygon_vertex_index] < 0, (HashMap<int, R>()), "FBX file seems corrupted: #ERR10."); + ERR_FAIL_COND_V_MSG(p_mapping_data.index[polygon_vertex_index] >= (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR11."); + aggregate_vertex_data[vertex_index].push_back({ polygon_id, p_mapping_data.data[p_mapping_data.index[polygon_vertex_index]] }); + } + } + } break; + case FBXDocParser::MeshGeometry::MapType::polygon: { + if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { + // The data are mapped per polygon directly. + const int polygon_count = count_polygons(p_mesh_indices); + ERR_FAIL_COND_V_MSG(polygon_count != (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR12"); + + // Advance each polygon vertex, each new polygon advance the polygon index. + int polygon_index = -1; + for (size_t polygon_vertex_index = 0; + polygon_vertex_index < p_mesh_indices.size(); + polygon_vertex_index += 1) { + if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { + polygon_index += 1; + ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR13"); + } + + const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); + ERR_FAIL_INDEX_V_MSG(vertex_index, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR14"); + + aggregate_vertex_data[vertex_index].push_back({ polygon_index, p_mapping_data.data[polygon_index] }); + } + ERR_FAIL_COND_V_MSG((polygon_index + 1) != polygon_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR16. Not all Polygons are present in the file."); + } else { + // The data is mapped per polygon using a reference. + // The indices array, contains a *reference_id for each polygon. + // * Note that the reference_id is the id of data into the data array. + // + // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html + const int polygon_count = count_polygons(p_mesh_indices); + ERR_FAIL_COND_V_MSG(polygon_count != (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR17"); + + // Advance each polygon vertex, each new polygon advance the polygon index. + int polygon_index = -1; + for (size_t polygon_vertex_index = 0; + polygon_vertex_index < p_mesh_indices.size(); + polygon_vertex_index += 1) { + if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { + polygon_index += 1; + ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR18"); + ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[polygon_index], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR19"); + } + + const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); + ERR_FAIL_INDEX_V_MSG(vertex_index, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR20"); + + aggregate_vertex_data[vertex_index].push_back({ polygon_index, p_mapping_data.data[p_mapping_data.index[polygon_index]] }); + } + ERR_FAIL_COND_V_MSG((polygon_index + 1) != polygon_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR22. Not all Polygons are present in the file."); + } + } break; + case FBXDocParser::MeshGeometry::MapType::edge: { + if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { + // The data are mapped per edge directly. + ERR_FAIL_COND_V_MSG(p_edge_map.size() != p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR23"); + for (size_t edge_index = 0; edge_index < p_mapping_data.data.size(); edge_index += 1) { + const FBXDocParser::MeshGeometry::Edge edge = FBXDocParser::MeshGeometry::get_edge(p_edge_map, edge_index); + ERR_FAIL_INDEX_V_MSG(edge.vertex_0, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR24"); + ERR_FAIL_INDEX_V_MSG(edge.vertex_1, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR25"); + ERR_FAIL_INDEX_V_MSG(edge.vertex_0, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR26"); + ERR_FAIL_INDEX_V_MSG(edge.vertex_1, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR27"); + aggregate_vertex_data[edge.vertex_0].push_back({ -1, p_mapping_data.data[edge_index] }); + aggregate_vertex_data[edge.vertex_1].push_back({ -1, p_mapping_data.data[edge_index] }); + } + } else { + // The data is mapped per edge using a reference. + // The indices array, contains a *reference_id for each polygon. + // * Note that the reference_id is the id of data into the data array. + // + // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html + ERR_FAIL_COND_V_MSG(p_edge_map.size() != p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR28"); + for (size_t edge_index = 0; edge_index < p_mapping_data.data.size(); edge_index += 1) { + const FBXDocParser::MeshGeometry::Edge edge = FBXDocParser::MeshGeometry::get_edge(p_edge_map, edge_index); + ERR_FAIL_INDEX_V_MSG(edge.vertex_0, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR29"); + ERR_FAIL_INDEX_V_MSG(edge.vertex_1, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR30"); + ERR_FAIL_INDEX_V_MSG(edge.vertex_0, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR31"); + ERR_FAIL_INDEX_V_MSG(edge.vertex_1, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR32"); + ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[edge.vertex_0], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR33"); + ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[edge.vertex_1], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR34"); + aggregate_vertex_data[edge.vertex_0].push_back({ -1, p_mapping_data.data[p_mapping_data.index[edge_index]] }); + aggregate_vertex_data[edge.vertex_1].push_back({ -1, p_mapping_data.data[p_mapping_data.index[edge_index]] }); + } + } + } break; + case FBXDocParser::MeshGeometry::MapType::all_the_same: { + // No matter the mode, no matter the data size; The first always win + // and is set to all the vertices. + ERR_FAIL_COND_V_MSG(p_mapping_data.data.size() <= 0, (HashMap<int, R>()), "FBX file seems corrupted: #ERR35"); + if (p_mapping_data.data.size() > 0) { + for (int vertex_index = 0; vertex_index < p_vertex_count; vertex_index += 1) { + aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[0] }); + } + } + } break; + } + + if (aggregate_vertex_data.size() == 0) { + return (HashMap<int, R>()); + } + + // A map is used because turns out that the some FBX file are not well organized + // with vertices well compacted. Using a map allows avoid those issues. + HashMap<Vertex, R> result; + + // Aggregate the collected data. + for (const Vertex *index = aggregate_vertex_data.next(nullptr); index != nullptr; index = aggregate_vertex_data.next(index)) { + Vector<VertexData<T>> *aggregated_vertex = aggregate_vertex_data.getptr(*index); + // This can't be null because we are just iterating. + CRASH_COND(aggregated_vertex == nullptr); + + ERR_FAIL_INDEX_V_MSG(0, aggregated_vertex->size(), (HashMap<int, R>()), "The FBX file is corrupted, No valid data for this vertex index."); + result[*index] = collector_function(aggregated_vertex, p_fall_back); + } + + // Sanitize the data now, if the file is broken we can try import it anyway. + bool problem_found = false; + for (size_t i = 0; i < p_mesh_indices.size(); i += 1) { + const Vertex vertex = get_vertex_from_polygon_vertex(p_mesh_indices, i); + if (result.has(vertex) == false) { + result[vertex] = p_fall_back; + problem_found = true; + } + } + if (problem_found) { + WARN_PRINT("Some data is missing, this FBX file may be corrupted: #WARN0."); + } + + return result; +} + +template <class T> +HashMap<int, T> FBXMeshData::extract_per_polygon( + int p_vertex_count, + const std::vector<int> &p_polygon_indices, + const FBXDocParser::MeshGeometry::MappingData<T> &p_fbx_data, + T p_fallback_value) const { + ERR_FAIL_COND_V_MSG(p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_fbx_data.data.size() == 0, (HashMap<int, T>()), "invalid index to direct array"); + ERR_FAIL_COND_V_MSG(p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index && p_fbx_data.index.size() == 0, (HashMap<int, T>()), "The FBX seems corrupted"); + + const int polygon_count = count_polygons(p_polygon_indices); + + // Aggregate vertex data. + HashMap<int, Vector<T>> aggregate_polygon_data; + + switch (p_fbx_data.map_type) { + case FBXDocParser::MeshGeometry::MapType::none: { + // No data nothing to do. + return (HashMap<int, T>()); + } + case FBXDocParser::MeshGeometry::MapType::vertex: { + ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per vertex. This should not happen."); + } break; + case FBXDocParser::MeshGeometry::MapType::polygon_vertex: { + ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per polygon vertex. This should not happen."); + } break; + case FBXDocParser::MeshGeometry::MapType::polygon: { + if (p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct) { + // The data is stored efficiently index_to_direct allows less data in the FBX file. + for (int polygon_index = 0; + polygon_index < polygon_count; + polygon_index += 1) { + if (p_fbx_data.index.size() == 0) { + ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR62"); + aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[polygon_index]); + } else { + ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR62"); + + const int index_to_direct = p_fbx_data.index[polygon_index]; + T value = p_fbx_data.data[index_to_direct]; + aggregate_polygon_data[polygon_index].push_back(value); + } + } + } else if (p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { + // The data are mapped per polygon directly. + ERR_FAIL_COND_V_MSG(polygon_count != (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR51"); + + // Advance each polygon vertex, each new polygon advance the polygon index. + for (int polygon_index = 0; + polygon_index < polygon_count; + polygon_index += 1) { + ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR52"); + aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[polygon_index]); + } + } else { + // The data is mapped per polygon using a reference. + // The indices array, contains a *reference_id for each polygon. + // * Note that the reference_id is the id of data into the data array. + // + // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html + ERR_FAIL_COND_V_MSG(polygon_count != (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file seems corrupted: #ERR52"); + + // Advance each polygon vertex, each new polygon advance the polygon index. + for (int polygon_index = 0; + polygon_index < polygon_count; + polygon_index += 1) { + ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR53"); + ERR_FAIL_INDEX_V_MSG(p_fbx_data.index[polygon_index], (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR54"); + aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[p_fbx_data.index[polygon_index]]); + } + } + } break; + case FBXDocParser::MeshGeometry::MapType::edge: { + ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per edge. This should not happen."); + } break; + case FBXDocParser::MeshGeometry::MapType::all_the_same: { + // No matter the mode, no matter the data size; The first always win + // and is set to all the vertices. + ERR_FAIL_COND_V_MSG(p_fbx_data.data.size() <= 0, (HashMap<int, T>()), "FBX file seems corrupted: #ERR55"); + if (p_fbx_data.data.size() > 0) { + for (int polygon_index = 0; polygon_index < polygon_count; polygon_index += 1) { + aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[0]); + } + } + } break; + } + + if (aggregate_polygon_data.size() == 0) { + return (HashMap<int, T>()); + } + + // A map is used because turns out that the some FBX file are not well organized + // with vertices well compacted. Using a map allows avoid those issues. + HashMap<int, T> polygons; + + // Take the first value for each vertex. + for (const Vertex *index = aggregate_polygon_data.next(nullptr); index != nullptr; index = aggregate_polygon_data.next(index)) { + Vector<T> *aggregated_polygon = aggregate_polygon_data.getptr(*index); + // This can't be null because we are just iterating. + CRASH_COND(aggregated_polygon == nullptr); + + ERR_FAIL_INDEX_V_MSG(0, (int)aggregated_polygon->size(), (HashMap<int, T>()), "The FBX file is corrupted, No valid data for this polygon index."); + + // Validate the final value. + polygons[*index] = (*aggregated_polygon)[0]; + } + + // Sanitize the data now, if the file is broken we can try import it anyway. + bool problem_found = false; + for (int polygon_i = 0; polygon_i < polygon_count; polygon_i += 1) { + if (polygons.has(polygon_i) == false) { + polygons[polygon_i] = p_fallback_value; + problem_found = true; + } + } + if (problem_found) { + WARN_PRINT("Some data is missing, this FBX file may be corrupted: #WARN1."); + } + + return polygons; +} + +void FBXMeshData::extract_morphs(const FBXDocParser::MeshGeometry *mesh_geometry, HashMap<String, MorphVertexData> &r_data) { + r_data.clear(); + + const int vertex_count = mesh_geometry->get_vertices().size(); + + for (const FBXDocParser::BlendShape *blend_shape : mesh_geometry->get_blend_shapes()) { + for (const FBXDocParser::BlendShapeChannel *blend_shape_channel : blend_shape->BlendShapeChannels()) { + const std::vector<const FBXDocParser::ShapeGeometry *> &shape_geometries = blend_shape_channel->GetShapeGeometries(); + for (const FBXDocParser::ShapeGeometry *shape_geometry : shape_geometries) { + String morph_name = ImportUtils::FBXAnimMeshName(shape_geometry->Name()).c_str(); + if (morph_name.is_empty()) { + morph_name = "morph"; + } + + // TODO we have only these?? + const std::vector<unsigned int> &morphs_vertex_indices = shape_geometry->GetIndices(); + const std::vector<Vector3> &morphs_vertices = shape_geometry->GetVertices(); + const std::vector<Vector3> &morphs_normals = shape_geometry->GetNormals(); + + ERR_FAIL_COND_MSG((int)morphs_vertex_indices.size() > vertex_count, "The FBX file is corrupted: #ERR103"); + ERR_FAIL_COND_MSG(morphs_vertex_indices.size() != morphs_vertices.size(), "The FBX file is corrupted: #ERR104"); + ERR_FAIL_COND_MSG((int)morphs_vertices.size() > vertex_count, "The FBX file is corrupted: #ERR105"); + ERR_FAIL_COND_MSG(morphs_normals.size() != 0 && morphs_normals.size() != morphs_vertices.size(), "The FBX file is corrupted: #ERR106"); + + if (r_data.has(morph_name) == false) { + // This morph doesn't exist yet. + // Create it. + MorphVertexData md; + md.vertices.resize(vertex_count); + md.normals.resize(vertex_count); + r_data.set(morph_name, md); + } + + MorphVertexData *data = r_data.getptr(morph_name); + Vector3 *data_vertices_ptr = data->vertices.ptrw(); + Vector3 *data_normals_ptr = data->normals.ptrw(); + + for (int i = 0; i < (int)morphs_vertex_indices.size(); i += 1) { + const Vertex vertex = morphs_vertex_indices[i]; + + ERR_FAIL_INDEX_MSG(vertex, vertex_count, "The blend shapes of this FBX file are corrupted. It has a not valid vertex."); + + data_vertices_ptr[vertex] = morphs_vertices[i]; + + if (morphs_normals.size() != 0) { + data_normals_ptr[vertex] = morphs_normals[i]; + } + } + } + } + } +} diff --git a/modules/fbx/data/fbx_mesh_data.h b/modules/fbx/data/fbx_mesh_data.h new file mode 100644 index 0000000000..b6e87bb9e2 --- /dev/null +++ b/modules/fbx/data/fbx_mesh_data.h @@ -0,0 +1,184 @@ +/*************************************************************************/ +/* fbx_mesh_data.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 FBX_MESH_DATA_H +#define FBX_MESH_DATA_H + +#include "core/templates/hash_map.h" +#include "editor/import/resource_importer_scene.h" +#include "editor/import/scene_importer_mesh_node_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/resources/surface_tool.h" + +#include "fbx_bone.h" +#include "fbx_parser/FBXMeshGeometry.h" +#include "import_state.h" +#include "tools/import_utils.h" + +struct FBXNode; +struct FBXMeshData; +struct FBXBone; +struct ImportState; + +struct VertexWeightMapping { + Vector<real_t> weights; + Vector<int> bones; + // This extra vector is used because the bone id is computed in a second step. + // TODO Get rid of this extra step is a good idea. + Vector<Ref<FBXBone>> bones_ref; +}; + +template <class T> +struct VertexData { + int polygon_index; + T data; +}; + +// Caches mesh information and instantiates meshes for you using helper functions. +struct FBXMeshData : Reference { + struct MorphVertexData { + // TODO we have only these?? + /// Each element is a vertex. Not supposed to be void. + Vector<Vector3> vertices; + /// Each element is a vertex. Not supposed to be void. + Vector<Vector3> normals; + }; + + // FIXME: remove this is a hack for testing only + mutable const FBXDocParser::MeshGeometry *mesh_geometry = nullptr; + + Ref<FBXNode> mesh_node = nullptr; + /// vertex id, Weight Info + /// later: perf we can use array here + HashMap<int, VertexWeightMapping> vertex_weights; + + // translate fbx mesh data from document context to FBX Mesh Geometry Context + bool valid_weight_indexes = false; + + EditorSceneImporterMeshNode3D *create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression); + + void gen_weight_info(Ref<SurfaceTool> st, int vertex_id) const; + + /* mesh maximum weight count */ + bool valid_weight_count = false; + int max_weight_count = 0; + uint64_t armature_id = 0; + bool valid_armature_id = false; + EditorSceneImporterMeshNode3D *godot_mesh_instance = nullptr; + +private: + void sanitize_vertex_weights(const ImportState &state); + + /// Make sure to reorganize the vertices so that the correct UV is taken. + /// This step is needed because differently from the normal, that can be + /// combined, the UV may need its own triangle because sometimes they have + /// really different UV for the same vertex but different polygon. + /// This function make sure to add another vertex for those UVS. + void reorganize_vertices( + std::vector<int> &r_polygon_indices, + std::vector<Vector3> &r_vertices, + HashMap<int, Vector3> &r_normals, + HashMap<int, Vector2> &r_uv_1, + HashMap<int, Vector2> &r_uv_2, + HashMap<int, Color> &r_color, + HashMap<String, MorphVertexData> &r_morphs, + HashMap<int, HashMap<int, Vector3>> &r_normals_raw, + HashMap<int, HashMap<int, Color>> &r_colors_raw, + HashMap<int, HashMap<int, Vector2>> &r_uv_1_raw, + HashMap<int, HashMap<int, Vector2>> &r_uv_2_raw); + + void add_vertex( + const ImportState &state, + Ref<SurfaceTool> p_surface_tool, + real_t p_scale, + int p_vertex, + const std::vector<Vector3> &p_vertices_position, + const HashMap<int, Vector3> &p_normals, + const HashMap<int, Vector2> &p_uvs_0, + const HashMap<int, Vector2> &p_uvs_1, + const HashMap<int, Color> &p_colors, + const Vector3 &p_morph_value = Vector3(), + const Vector3 &p_morph_normal = Vector3()); + + void triangulate_polygon(Ref<SurfaceTool> st, Vector<int> p_polygon_vertex, Vector<int> p_surface_vertex_map, const std::vector<Vector3> &p_vertices) const; + + /// This function is responsible to convert the FBX polygon vertex to + /// vertex index. + /// The polygon vertices are stored in an array with some negative + /// values. The negative values define the last face index. + /// For example the following `face_array` contains two faces, the former + /// with 3 vertices and the latter with a line: + /// [0,2,-2,3,-5] + /// Parsed as: + /// [0, 2, 1, 3, 4] + /// The negative values are computed using this formula: `(-value) - 1` + /// + /// Returns the vertex index from the poligon vertex. + /// Returns -1 if `p_index` is invalid. + int get_vertex_from_polygon_vertex(const std::vector<int> &p_face_indices, int p_index) const; + + /// Returns true if this polygon_vertex_index is the end of a new polygon. + bool is_end_of_polygon(const std::vector<int> &p_face_indices, int p_index) const; + + /// Returns true if this polygon_vertex_index is the begin of a new polygon. + bool is_start_of_polygon(const std::vector<int> &p_face_indices, int p_index) const; + + /// Returns the number of polygons. + int count_polygons(const std::vector<int> &p_face_indices) const; + + /// Used to extract data from the `MappingData` aligned with vertex. + /// Useful to extract normal/uvs/colors/tangents/etc... + /// If the function fails somehow, it returns an hollow vector and print an error. + template <class R, class T> + HashMap<int, R> extract_per_vertex_data( + int p_vertex_count, + const std::vector<FBXDocParser::MeshGeometry::Edge> &p_edges, + const std::vector<int> &p_mesh_indices, + const FBXDocParser::MeshGeometry::MappingData<T> &p_mapping_data, + R (*collector_function)(const Vector<VertexData<T>> *p_vertex_data, R p_fall_back), + R p_fall_back) const; + + /// Used to extract data from the `MappingData` organized per polygon. + /// Useful to extract the material + /// If the function fails somehow, it returns an hollow vector and print an error. + template <class T> + HashMap<int, T> extract_per_polygon( + int p_vertex_count, + const std::vector<int> &p_face_indices, + const FBXDocParser::MeshGeometry::MappingData<T> &p_fbx_data, + T p_fallback_value) const; + + /// Extracts the morph data and organizes it per vertices. + /// The returned `MorphVertexData` arrays are never something different + /// then the `vertex_count`. + void extract_morphs(const FBXDocParser::MeshGeometry *mesh_geometry, HashMap<String, MorphVertexData> &r_data); +}; + +#endif // FBX_MESH_DATA_H diff --git a/modules/fbx/data/fbx_node.h b/modules/fbx/data/fbx_node.h new file mode 100644 index 0000000000..d8efcaa982 --- /dev/null +++ b/modules/fbx/data/fbx_node.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* fbx_node.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 FBX_NODE_H +#define FBX_NODE_H + +#include "fbx_skeleton.h" +#include "model_abstraction.h" +#include "pivot_transform.h" + +#include "fbx_parser/FBXDocument.h" + +class Node3D; +struct PivotTransform; + +struct FBXNode : Reference, ModelAbstraction { + uint64_t current_node_id = 0; + String node_name = String(); + Node3D *godot_node = nullptr; + + // used to parent the skeleton once the tree is built. + Ref<FBXSkeleton> skeleton_node = Ref<FBXSkeleton>(); + + void set_parent(Ref<FBXNode> p_parent) { + fbx_parent = p_parent; + } + + void set_pivot_transform(Ref<PivotTransform> p_pivot_transform) { + pivot_transform = p_pivot_transform; + } + + Ref<PivotTransform> pivot_transform = Ref<PivotTransform>(); // local and global xform data + Ref<FBXNode> fbx_parent = Ref<FBXNode>(); // parent node +}; + +#endif // FBX_NODE_H diff --git a/modules/fbx/data/fbx_skeleton.cpp b/modules/fbx/data/fbx_skeleton.cpp new file mode 100644 index 0000000000..93aeffc132 --- /dev/null +++ b/modules/fbx/data/fbx_skeleton.cpp @@ -0,0 +1,123 @@ +/*************************************************************************/ +/* fbx_skeleton.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "fbx_skeleton.h" + +#include "import_state.h" + +#include "tools/import_utils.h" + +void FBXSkeleton::init_skeleton(const ImportState &state) { + int skeleton_bone_count = skeleton_bones.size(); + + if (skeleton == nullptr && skeleton_bone_count > 0) { + skeleton = memnew(Skeleton3D); + + if (fbx_node.is_valid()) { + // cache skeleton attachment for later during node creation + // can't be done until after node hierarchy is built + if (fbx_node->godot_node != state.root) { + fbx_node->skeleton_node = Ref<FBXSkeleton>(this); + print_verbose("cached armature skeleton attachment for node " + fbx_node->node_name); + } else { + // root node must never be a skeleton to prevent cyclic skeletons from being allowed (skeleton in a skeleton) + fbx_node->godot_node->add_child(skeleton); + skeleton->set_owner(state.root_owner); + skeleton->set_name("Skeleton3D"); + print_verbose("created armature skeleton for root"); + } + } else { + memfree(skeleton); + skeleton = nullptr; + print_error("[doc] skeleton has no valid node to parent nodes to - erasing"); + skeleton_bones.clear(); + return; + } + } + + // Make the bone name uniques. + for (int x = 0; x < skeleton_bone_count; x++) { + Ref<FBXBone> bone = skeleton_bones[x]; + if (bone.is_valid()) { + // Make sure the bone name is unique. + const String bone_name = bone->bone_name; + int same_name_count = 0; + for (int y = x; y < skeleton_bone_count; y++) { + Ref<FBXBone> other_bone = skeleton_bones[y]; + if (other_bone.is_valid()) { + if (other_bone->bone_name == bone_name) { + same_name_count += 1; + other_bone->bone_name += "_" + itos(same_name_count); + } + } + } + } + } + + Map<int, Ref<FBXBone>> bone_map; + // implement fbx cluster skin logic here this is where it goes + int bone_count = 0; + for (int x = 0; x < skeleton_bone_count; x++) { + Ref<FBXBone> bone = skeleton_bones[x]; + if (bone.is_valid()) { + skeleton->add_bone(bone->bone_name); + bone->godot_bone_id = bone_count; + bone->fbx_skeleton = Ref<FBXSkeleton>(this); + bone_map.insert(bone_count, bone); + print_verbose("added bone " + itos(bone->bone_id) + " " + bone->bone_name); + bone_count++; + } + } + + ERR_FAIL_COND_MSG(skeleton->get_bone_count() != bone_count, "Not all bones got added, is the file corrupted?"); + + for (Map<int, Ref<FBXBone>>::Element *bone_element = bone_map.front(); bone_element; bone_element = bone_element->next()) { + const Ref<FBXBone> bone = bone_element->value(); + int bone_index = bone_element->key(); + print_verbose("working on bone: " + itos(bone_index) + " bone name:" + bone->bone_name); + + skeleton->set_bone_rest(bone->godot_bone_id, get_unscaled_transform(bone->node->pivot_transform->LocalTransform, state.scale)); + + // lookup parent ID + if (bone->valid_parent && state.fbx_bone_map.has(bone->parent_bone_id)) { + Ref<FBXBone> parent_bone = state.fbx_bone_map[bone->parent_bone_id]; + int bone_id = skeleton->find_bone(parent_bone->bone_name); + if (bone_id != -1) { + skeleton->set_bone_parent(bone_index, bone_id); + } else { + print_error("invalid bone parent: " + parent_bone->bone_name); + } + } else { + if (bone->godot_bone_id != -1) { + skeleton->set_bone_parent(bone_index, -1); // no parent for this bone + } + } + } +} diff --git a/modules/fbx/data/fbx_skeleton.h b/modules/fbx/data/fbx_skeleton.h new file mode 100644 index 0000000000..a2e330114c --- /dev/null +++ b/modules/fbx/data/fbx_skeleton.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* fbx_skeleton.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 FBX_SKELETON_H +#define FBX_SKELETON_H + +#include "fbx_bone.h" +#include "fbx_node.h" +#include "model_abstraction.h" + +#include "core/object/reference.h" +#include "scene/3d/skeleton_3d.h" + +struct FBXNode; +struct ImportState; +struct FBXBone; + +struct FBXSkeleton : Reference { + Ref<FBXNode> fbx_node = Ref<FBXNode>(); + Vector<Ref<FBXBone>> skeleton_bones = Vector<Ref<FBXBone>>(); + Skeleton3D *skeleton = nullptr; + + void init_skeleton(const ImportState &state); +}; + +#endif // FBX_SKELETON_H diff --git a/modules/assimp/import_state.h b/modules/fbx/data/import_state.h index a1cce6968b..34021ca7b1 100644 --- a/modules/assimp/import_state.h +++ b/modules/fbx/data/import_state.h @@ -28,8 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef EDITOR_SCENE_IMPORT_STATE_H -#define EDITOR_SCENE_IMPORT_STATE_H +#ifndef IMPORT_STATE_H +#define IMPORT_STATE_H + +#include "fbx_mesh_data.h" +#include "tools/import_utils.h" +#include "tools/validation_tools.h" + +#include "pivot_transform.h" #include "core/core_bind.h" #include "core/io/resource_importer.h" @@ -43,90 +49,64 @@ #include "scene/resources/animation.h" #include "scene/resources/surface_tool.h" -#include <assimp/matrix4x4.h> -#include <assimp/scene.h> -#include <assimp/types.h> -#include <assimp/DefaultLogger.hpp> -#include <assimp/LogStream.hpp> -#include <assimp/Logger.hpp> - -namespace AssimpImporter { -/** Import state is for global scene import data - * This makes the code simpler and contains useful lookups. - */ +#include "modules/fbx/fbx_parser/FBXDocument.h" +#include "modules/fbx/fbx_parser/FBXImportSettings.h" +#include "modules/fbx/fbx_parser/FBXMeshGeometry.h" +#include "modules/fbx/fbx_parser/FBXParser.h" +#include "modules/fbx/fbx_parser/FBXTokenizer.h" +#include "modules/fbx/fbx_parser/FBXUtil.h" + +struct FBXBone; +struct FBXMeshData; +struct FBXNode; +struct FBXSkeleton; + struct ImportState { - String path; - Node3D *root; - const aiScene *assimp_scene; - uint32_t max_bone_weights; - - Map<String, Ref<Mesh>> mesh_cache; - Map<int, Ref<Material>> material_cache; - Map<String, int> light_cache; - Map<String, int> camera_cache; - - // very useful for when you need to ask assimp for the bone mesh - - Map<const aiNode *, Node *> assimp_node_map; - Map<String, Ref<Image>> path_to_image_cache; - - // Generation 3 - determinisitic iteration - // to lower potential recursion errors - List<const aiNode *> nodes; - Map<const aiNode *, Node3D *> flat_node_map; - AnimationPlayer *animation_player; - - // Generation 3 - deterministic armatures - // list of armature nodes - flat and simple to parse - // assimp node, node in godot - List<aiNode *> armature_nodes; - Map<const aiNode *, Skeleton3D *> armature_skeletons; - Map<aiBone *, Skeleton3D *> skeleton_bone_map; - // Generation 3 - deterministic bone handling - // bones from the stack are popped when found - // this means we can detect - // what bones are for other armatures - List<aiBone *> bone_stack; - - // EditorSceneImporter::ImportFlags - uint32_t import_flags; -}; + bool enable_material_import = true; + bool enable_animation_import = true; -struct AssimpImageData { - Ref<Image> raw_image; - Ref<ImageTexture> texture; - aiTextureMapMode map_mode[2]; -}; + Map<StringName, Ref<Texture>> cached_image_searches; + Map<uint64_t, Ref<Material>> cached_materials; + + String path = String(); + Node3D *root_owner = nullptr; + Node3D *root = nullptr; + real_t scale = 0.01; + Ref<FBXNode> fbx_root_node = Ref<FBXNode>(); + // skeleton map - merged automatically when they are on the same x node in the tree so we can merge them automatically. + Map<uint64_t, Ref<FBXSkeleton>> skeleton_map = Map<uint64_t, Ref<FBXSkeleton>>(); + + // nodes on the same level get merged automatically. + //Map<uint64_t, Skeleton3D *> armature_map; + AnimationPlayer *animation_player = nullptr; + + // Generation 4 - Raw document accessing for bone/skin/joint/kLocators + // joints are not necessarily bones but must be merged into the skeleton + // (bone id), bone + Map<uint64_t, Ref<FBXBone>> fbx_bone_map = Map<uint64_t, Ref<FBXBone>>(); // this is the bone name and setup information required for joints + // this will never contain joints only bones attached to a mesh. + + // Generation 4 - Raw document for creating the nodes transforms in the scene + // this is a list of the nodes in the scene + // (id, node) + List<Ref<FBXNode>> fbx_node_list = List<Ref<FBXNode>>(); + + // All nodes which have been created in the scene + // this will not contain the root node of the scene + Map<uint64_t, Ref<FBXNode>> fbx_target_map = Map<uint64_t, Ref<FBXNode>>(); + + // mesh nodes which are created in node / mesh step - used for populating skin poses in MeshSkins + Map<uint64_t, Ref<FBXNode>> MeshNodes = Map<uint64_t, Ref<FBXNode>>(); + // mesh skin map + Map<uint64_t, Ref<Skin>> MeshSkins = Map<uint64_t, Ref<Skin>>(); -/** Recursive state is used to push state into functions instead of specifying them - * This makes the code easier to handle too and add extra arguments without breaking things - */ -struct RecursiveState { - RecursiveState() {} // do not construct :) - RecursiveState( - Transform &_node_transform, - Skeleton3D *_skeleton, - Node3D *_new_node, - String &_node_name, - aiNode *_assimp_node, - Node *_parent_node, - aiBone *_bone) : - node_transform(_node_transform), - skeleton(_skeleton), - new_node(_new_node), - node_name(_node_name), - assimp_node(_assimp_node), - parent_node(_parent_node), - bone(_bone) {} - - Transform node_transform; - Skeleton3D *skeleton = nullptr; - Node3D *new_node = nullptr; - String node_name; - aiNode *assimp_node = nullptr; - Node *parent_node = nullptr; - aiBone *bone = nullptr; + // this is the container for the mesh weight information and eventually + // any mesh data + // but not the skin, just stuff important for rendering + // skin is applied to mesh instance so not really required to be in here yet. + // maybe later + // fbx mesh id, FBXMeshData + Map<uint64_t, Ref<FBXMeshData>> renderer_mesh_data = Map<uint64_t, Ref<FBXMeshData>>(); }; -} // namespace AssimpImporter -#endif // EDITOR_SCENE_IMPORT_STATE_H +#endif // IMPORT_STATE_H diff --git a/modules/fbx/data/model_abstraction.h b/modules/fbx/data/model_abstraction.h new file mode 100644 index 0000000000..b21ab0badb --- /dev/null +++ b/modules/fbx/data/model_abstraction.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* model_abstraction.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 MODEL_ABSTRACTION_H +#define MODEL_ABSTRACTION_H + +#include "modules/fbx/fbx_parser/FBXDocument.h" + +struct ModelAbstraction { + mutable const FBXDocParser::Model *fbx_model = nullptr; + + void set_model(const FBXDocParser::Model *p_model) { + fbx_model = p_model; + } + + bool has_model() const { + return fbx_model != nullptr; + } + + const FBXDocParser::Model *get_model() const { + return fbx_model; + } +}; + +#endif // MODEL_ABSTRACTION_H diff --git a/modules/fbx/data/pivot_transform.cpp b/modules/fbx/data/pivot_transform.cpp new file mode 100644 index 0000000000..c12e94097f --- /dev/null +++ b/modules/fbx/data/pivot_transform.cpp @@ -0,0 +1,294 @@ +/*************************************************************************/ +/* pivot_transform.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "pivot_transform.h" + +#include "tools/import_utils.h" + +void PivotTransform::ReadTransformChain() { + const FBXDocParser::PropertyTable *props = fbx_model->Props(); + const FBXDocParser::Model::RotOrder &rot = fbx_model->RotationOrder(); + const FBXDocParser::TransformInheritance &inheritType = fbx_model->InheritType(); + inherit_type = inheritType; // copy the inherit type we need it in the second step. + print_verbose("Model: " + String(fbx_model->Name().c_str()) + " Has inherit type: " + itos(fbx_model->InheritType())); + bool ok = false; + raw_pre_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "PreRotation", ok)); + if (ok) { + pre_rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(raw_pre_rotation)); + print_verbose("valid pre_rotation: " + raw_pre_rotation + " euler conversion: " + (pre_rotation.get_euler() * (180 / Math_PI))); + } + raw_post_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "PostRotation", ok)); + if (ok) { + post_rotation = ImportUtils::EulerToQuaternion(FBXDocParser::Model::RotOrder_EulerXYZ, ImportUtils::deg2rad(raw_post_rotation)); + print_verbose("valid post_rotation: " + raw_post_rotation + " euler conversion: " + (pre_rotation.get_euler() * (180 / Math_PI))); + } + const Vector3 &RotationPivot = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "RotationPivot", ok)); + if (ok) { + rotation_pivot = ImportUtils::FixAxisConversions(RotationPivot); + } + const Vector3 &RotationOffset = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "RotationOffset", ok)); + if (ok) { + rotation_offset = ImportUtils::FixAxisConversions(RotationOffset); + } + const Vector3 &ScalingOffset = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "ScalingOffset", ok)); + if (ok) { + scaling_offset = ImportUtils::FixAxisConversions(ScalingOffset); + } + const Vector3 &ScalingPivot = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "ScalingPivot", ok)); + if (ok) { + scaling_pivot = ImportUtils::FixAxisConversions(ScalingPivot); + } + const Vector3 &Translation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Translation", ok)); + if (ok) { + translation = ImportUtils::FixAxisConversions(Translation); + } + raw_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Rotation", ok)); + if (ok) { + rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(raw_rotation)); + } + const Vector3 &Scaling = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Scaling", ok)); + if (ok) { + scaling = Scaling; + } + const Vector3 &GeometricScaling = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricScaling", ok)); + if (ok) { + geometric_scaling = GeometricScaling; + } else { + geometric_scaling = Vector3(0, 0, 0); + } + + const Vector3 &GeometricRotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricRotation", ok)); + if (ok) { + geometric_rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(GeometricRotation)); + } else { + geometric_rotation = Quat(); + } + + const Vector3 &GeometricTranslation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricTranslation", ok)); + if (ok) { + geometric_translation = ImportUtils::FixAxisConversions(GeometricTranslation); + } else { + geometric_translation = Vector3(0, 0, 0); + } + + if (geometric_rotation != Quat()) { + print_error("geometric rotation is unsupported!"); + //CRASH_COND(true); + } + + if (!geometric_scaling.is_equal_approx(Vector3(1, 1, 1))) { + print_error("geometric scaling is unsupported!"); + //CRASH_COND(true); + } + + if (!geometric_translation.is_equal_approx(Vector3(0, 0, 0))) { + print_error("geometric translation is unsupported."); + //CRASH_COND(true); + } +} + +Transform PivotTransform::ComputeLocalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const { + Transform T, Roff, Rp, Soff, Sp, S; + + // Here I assume this is the operation which needs done. + // Its WorldTransform * V + + // Origin pivots + T.set_origin(p_translation); + Roff.set_origin(rotation_offset); + Rp.set_origin(rotation_pivot); + Soff.set_origin(scaling_offset); + Sp.set_origin(scaling_pivot); + + // Scaling node + S.scale(p_scaling); + // Rotation pivots + Transform Rpre = Transform(pre_rotation); + Transform R = Transform(p_rotation); + Transform Rpost = Transform(post_rotation); + + return T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse(); +} + +Transform PivotTransform::ComputeGlobalTransform(Transform t) const { + Vector3 pos = t.origin; + Vector3 scale = t.basis.get_scale(); + Quat rot = t.basis.get_rotation_quat(); + return ComputeGlobalTransform(pos, rot, scale); +} + +Transform PivotTransform::ComputeLocalTransform(Transform t) const { + Vector3 pos = t.origin; + Vector3 scale = t.basis.get_scale(); + Quat rot = t.basis.get_rotation_quat(); + return ComputeLocalTransform(pos, rot, scale); +} + +Transform PivotTransform::ComputeGlobalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const { + Transform T, Roff, Rp, Soff, Sp, S; + + // Here I assume this is the operation which needs done. + // Its WorldTransform * V + + // Origin pivots + T.set_origin(p_translation); + Roff.set_origin(rotation_offset); + Rp.set_origin(rotation_pivot); + Soff.set_origin(scaling_offset); + Sp.set_origin(scaling_pivot); + + // Scaling node + S.scale(p_scaling); + + // Rotation pivots + Transform Rpre = Transform(pre_rotation); + Transform R = Transform(p_rotation); + Transform Rpost = Transform(post_rotation); + + Transform parent_global_xform; + Transform parent_local_scaling_m; + + if (parent_transform.is_valid()) { + parent_global_xform = parent_transform->GlobalTransform; + parent_local_scaling_m = parent_transform->Local_Scaling_Matrix; + } + + Transform local_rotation_m, parent_global_rotation_m; + Quat parent_global_rotation = parent_global_xform.basis.get_rotation_quat(); + parent_global_rotation_m.basis.set_quat(parent_global_rotation); + local_rotation_m = Rpre * R * Rpost; + + //Basis parent_global_rotation = Basis(parent_global_xform.get_basis().get_rotation_quat().normalized()); + + Transform local_shear_scaling, parent_shear_scaling, parent_shear_rotation, parent_shear_translation; + Vector3 parent_translation = parent_global_xform.get_origin(); + parent_shear_translation.origin = parent_translation; + parent_shear_rotation = parent_shear_translation.affine_inverse() * parent_global_xform; + parent_shear_scaling = parent_global_rotation_m.affine_inverse() * parent_shear_rotation; + local_shear_scaling = S; + + // Inherit type handler - we don't care about T here, just reordering RSrs etc. + Transform global_rotation_scale; + if (inherit_type == FBXDocParser::Transform_RrSs) { + global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_shear_scaling * local_shear_scaling; + } else if (inherit_type == FBXDocParser::Transform_RSrs) { + global_rotation_scale = parent_global_rotation_m * parent_shear_scaling * local_rotation_m * local_shear_scaling; + } else if (inherit_type == FBXDocParser::Transform_Rrs) { + Transform parent_global_shear_m_noLocal = parent_shear_scaling * parent_local_scaling_m.affine_inverse(); + global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_global_shear_m_noLocal * local_shear_scaling; + } + Transform local_transform = T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse(); + //Transform local_translation_pivoted = Transform(Basis(), LocalTransform.origin); + + // manual hack to force SSC not to be compensated for - until we can handle it properly with tests + return parent_global_xform * local_transform; +} + +void PivotTransform::ComputePivotTransform() { + Transform T, Roff, Rp, Soff, Sp, S; + + // Here I assume this is the operation which needs done. + // Its WorldTransform * V + + // Origin pivots + T.set_origin(translation); + Roff.set_origin(rotation_offset); + Rp.set_origin(rotation_pivot); + Soff.set_origin(scaling_offset); + Sp.set_origin(scaling_pivot); + + // Scaling node + if (!scaling.is_equal_approx(Vector3())) { + S.scale(scaling); + } else { + S.scale(Vector3(1, 1, 1)); + } + Local_Scaling_Matrix = S; // copy for when node / child is looking for the value of this. + + // Rotation pivots + Transform Rpre = Transform(pre_rotation); + Transform R = Transform(rotation); + Transform Rpost = Transform(post_rotation); + + Transform parent_global_xform; + Transform parent_local_scaling_m; + + if (parent_transform.is_valid()) { + parent_global_xform = parent_transform->GlobalTransform; + parent_local_scaling_m = parent_transform->Local_Scaling_Matrix; + } + + Transform local_rotation_m, parent_global_rotation_m; + Quat parent_global_rotation = parent_global_xform.basis.get_rotation_quat(); + parent_global_rotation_m.basis.set_quat(parent_global_rotation); + local_rotation_m = Rpre * R * Rpost; + + //Basis parent_global_rotation = Basis(parent_global_xform.get_basis().get_rotation_quat().normalized()); + + Transform local_shear_scaling, parent_shear_scaling, parent_shear_rotation, parent_shear_translation; + Vector3 parent_translation = parent_global_xform.get_origin(); + parent_shear_translation.origin = parent_translation; + parent_shear_rotation = parent_shear_translation.affine_inverse() * parent_global_xform; + parent_shear_scaling = parent_global_rotation_m.affine_inverse() * parent_shear_rotation; + local_shear_scaling = S; + + // Inherit type handler - we don't care about T here, just reordering RSrs etc. + Transform global_rotation_scale; + if (inherit_type == FBXDocParser::Transform_RrSs) { + global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_shear_scaling * local_shear_scaling; + } else if (inherit_type == FBXDocParser::Transform_RSrs) { + global_rotation_scale = parent_global_rotation_m * parent_shear_scaling * local_rotation_m * local_shear_scaling; + } else if (inherit_type == FBXDocParser::Transform_Rrs) { + Transform parent_global_shear_m_noLocal = parent_shear_scaling * parent_local_scaling_m.inverse(); + global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_global_shear_m_noLocal * local_shear_scaling; + } + LocalTransform = Transform(); + LocalTransform = T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse(); + + ERR_FAIL_COND_MSG(LocalTransform.basis.determinant() == 0, "invalid scale reset"); + + Transform local_translation_pivoted = Transform(Basis(), LocalTransform.origin); + GlobalTransform = Transform(); + //GlobalTransform = parent_global_xform * LocalTransform; + Transform global_origin = Transform(Basis(), parent_translation); + GlobalTransform = (global_origin * local_translation_pivoted) * global_rotation_scale; + + ImportUtils::debug_xform("local xform calculation", LocalTransform); + print_verbose("scale of node: " + S.basis.get_scale_local()); + print_verbose("---------------------------------------------------------------"); +} + +void PivotTransform::Execute() { + ReadTransformChain(); + ComputePivotTransform(); + + ImportUtils::debug_xform("global xform: ", GlobalTransform); + computed_global_xform = true; +} diff --git a/modules/fbx/data/pivot_transform.h b/modules/fbx/data/pivot_transform.h new file mode 100644 index 0000000000..025c8dd58d --- /dev/null +++ b/modules/fbx/data/pivot_transform.h @@ -0,0 +1,115 @@ +/*************************************************************************/ +/* pivot_transform.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 PIVOT_TRANSFORM_H +#define PIVOT_TRANSFORM_H + +#include "core/math/transform.h" +#include "core/object/reference.h" + +#include "model_abstraction.h" + +#include "fbx_parser/FBXDocument.h" +#include "tools/import_utils.h" + +enum TransformationComp { + TransformationComp_Translation, + TransformationComp_Scaling, + TransformationComp_Rotation, + TransformationComp_RotationOffset, + TransformationComp_RotationPivot, + TransformationComp_PreRotation, + TransformationComp_PostRotation, + TransformationComp_ScalingOffset, + TransformationComp_ScalingPivot, + TransformationComp_GeometricTranslation, + TransformationComp_GeometricRotation, + TransformationComp_GeometricScaling, + TransformationComp_MAXIMUM +}; +// Abstract away pivot data so its simpler to handle +struct PivotTransform : Reference, ModelAbstraction { + // at the end we want to keep geometric_ everything, post and pre rotation + // these are used during animation data processing / keyframe ingestion the rest can be simplified down / out. + Quat pre_rotation = Quat(); + Quat post_rotation = Quat(); + Quat rotation = Quat(); + Quat geometric_rotation = Quat(); + Vector3 rotation_pivot = Vector3(); + Vector3 rotation_offset = Vector3(); + Vector3 scaling_offset = Vector3(1.0, 1.0, 1.0); + Vector3 scaling_pivot = Vector3(1.0, 1.0, 1.0); + Vector3 translation = Vector3(); + Vector3 scaling = Vector3(1.0, 1.0, 1.0); + Vector3 geometric_scaling = Vector3(1.0, 1.0, 1.0); + Vector3 geometric_translation = Vector3(); + + Vector3 raw_rotation = Vector3(); + Vector3 raw_post_rotation = Vector3(); + Vector3 raw_pre_rotation = Vector3(); + + /* Read pivots from the document */ + void ReadTransformChain(); + + void debug_pivot_xform(String p_name) { + print_verbose("debugging node name: " + p_name); + print_verbose("raw rotation: " + raw_rotation * (180 / Math_PI)); + print_verbose("raw pre_rotation " + raw_pre_rotation * (180 / Math_PI)); + print_verbose("raw post_rotation " + raw_post_rotation * (180 / Math_PI)); + } + + Transform ComputeGlobalTransform(Transform t) const; + Transform ComputeLocalTransform(Transform t) const; + Transform ComputeGlobalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const; + Transform ComputeLocalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const; + + /* Extract into xforms and calculate once */ + void ComputePivotTransform(); + + /* Execute the command for the pivot generation */ + void Execute(); + + void set_parent(Ref<PivotTransform> p_parent) { + parent_transform = p_parent; + } + + bool computed_global_xform = false; + Ref<PivotTransform> parent_transform = Ref<PivotTransform>(); + //Transform chain[TransformationComp_MAXIMUM]; + + // cached for later use + Transform GlobalTransform = Transform(); + Transform LocalTransform = Transform(); + Transform Local_Scaling_Matrix = Transform(); // used for inherit type. + Transform GeometricTransform = Transform(); // 3DS max only + FBXDocParser::TransformInheritance inherit_type = FBXDocParser::TransformInheritance_MAX; // maya fbx requires this - sorry <3 +}; + +#endif // PIVOT_TRANSFORM_H diff --git a/modules/fbx/doc_classes/EditorSceneImporterFBX.xml b/modules/fbx/doc_classes/EditorSceneImporterFBX.xml new file mode 100644 index 0000000000..72b6e0f5fd --- /dev/null +++ b/modules/fbx/doc_classes/EditorSceneImporterFBX.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorSceneImporterFBX" inherits="EditorSceneImporter" version="3.2"> + <brief_description> + FBX 3D asset importer. + </brief_description> + <description> + This is an FBX 3D asset importer with full support for most FBX features. + If exporting a FBX scene from Autodesk Maya, use these FBX export settings: + [codeblock] + - Smoothing Groups + - Smooth Mesh + - Triangluate (for meshes with blend shapes) + - Bake Animation + - Resample All + - Deformed Models + - Skins + - Blend Shapes + - Curve Filters + - Constant Key Reducer + - Auto Tangents Only + - *Do not check* Constraints (as it will break the file) + - Can check Embed Media (embeds textures into the exported FBX file) + - Note that when importing embedded media, the texture and mesh will be a single immutable file. + - You will have to re-export then re-import the FBX if the texture has changed. + - Units: Centimeters + - Up Axis: Y + - Binary format in FBX 2017 + [/codeblock] + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp new file mode 100644 index 0000000000..d37befbbfd --- /dev/null +++ b/modules/fbx/editor_scene_importer_fbx.cpp @@ -0,0 +1,1424 @@ +/*************************************************************************/ +/* editor_scene_importer_fbx.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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_fbx.h" + +#include "data/fbx_anim_container.h" +#include "data/fbx_material.h" +#include "data/fbx_mesh_data.h" +#include "data/fbx_skeleton.h" +#include "tools/import_utils.h" + +#include "core/io/image_loader.h" +#include "editor/editor_log.h" +#include "editor/editor_node.h" +#include "editor/import/resource_importer_scene.h" +#include "editor/import/scene_importer_mesh_node_3d.h" +#include "scene/3d/bone_attachment_3d.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/light_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/main/node.h" +#include "scene/resources/material.h" + +#include "fbx_parser/FBXDocument.h" +#include "fbx_parser/FBXImportSettings.h" +#include "fbx_parser/FBXMeshGeometry.h" +#include "fbx_parser/FBXParser.h" +#include "fbx_parser/FBXProperties.h" +#include "fbx_parser/FBXTokenizer.h" + +#include <string> + +void EditorSceneImporterFBX::get_extensions(List<String> *r_extensions) const { + // register FBX as the one and only format for FBX importing + const String import_setting_string = "filesystem/import/fbx/"; + const String fbx_str = "fbx"; + Vector<String> exts; + exts.push_back(fbx_str); + _register_project_setting_import(fbx_str, import_setting_string, exts, r_extensions, + true); +} + +void EditorSceneImporterFBX::_register_project_setting_import(const String generic, + const String import_setting_string, + const Vector<String> &exts, + List<String> *r_extensions, + const bool p_enabled) const { + const String use_generic = "use_" + generic; + _GLOBAL_DEF(import_setting_string + use_generic, p_enabled, true); + if (ProjectSettings::get_singleton()->get(import_setting_string + use_generic)) { + for (int32_t i = 0; i < exts.size(); i++) { + r_extensions->push_back(exts[i]); + } + } +} + +uint32_t EditorSceneImporterFBX::get_import_flags() const { + return IMPORT_SCENE; +} + +Node3D *EditorSceneImporterFBX::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err) { + // done for performance when re-importing lots of files when testing importer in verbose only! + if (OS::get_singleton()->is_stdout_verbose()) { + EditorLog *log = EditorNode::get_log(); + log->clear(); + } + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + + ERR_FAIL_COND_V(!f, NULL); + + { + PackedByteArray data; + // broadphase tokenizing pass in which we identify the core + // syntax elements of FBX (brackets, commas, key:value mappings) + FBXDocParser::TokenList tokens; + + bool is_binary = false; + data.resize(f->get_len()); + f->get_buffer(data.ptrw(), data.size()); + PackedByteArray fbx_header; + fbx_header.resize(64); + for (int32_t byte_i = 0; byte_i < 64; byte_i++) { + fbx_header.ptrw()[byte_i] = data.ptr()[byte_i]; + } + + String fbx_header_string; + if (fbx_header.size() >= 0) { + fbx_header_string.parse_utf8((const char *)fbx_header.ptr(), fbx_header.size()); + } + + print_verbose("[doc] opening fbx file: " + p_path); + print_verbose("[doc] fbx header: " + fbx_header_string); + + // safer to check this way as there can be different formatted headers + if (fbx_header_string.find("Kaydara FBX Binary", 0) != -1) { + is_binary = true; + print_verbose("[doc] is binary"); + FBXDocParser::TokenizeBinary(tokens, (const char *)data.ptrw(), (size_t)data.size()); + } else { + print_verbose("[doc] is ascii"); + FBXDocParser::Tokenize(tokens, (const char *)data.ptrw(), (size_t)data.size()); + } + + // The import process explained: + // 1. Tokens are made, these are then taken into the 'parser' below + // 2. The parser constructs 'Elements' and all 'real' FBX Types. + // 3. This creates a problem: shared_ptr ownership, should Elements later 'take ownership' + // 4. No, it shouldn't so we should either a.) use weak ref for elements; but this is not correct. + + // use this information to construct a very rudimentary + // parse-tree representing the FBX scope structure + FBXDocParser::Parser parser(tokens, is_binary); + FBXDocParser::ImportSettings settings; + settings.strictMode = false; + + // this function leaks a lot + FBXDocParser::Document doc(parser, settings); + + // yeah so closing the file is a good idea (prevents readonly states) + f->close(); + + // safety for version handling + if (doc.IsSafeToImport()) { + bool is_blender_fbx = false; + //const FBXDocParser::PropertyPtr app_vendor = p_document->GlobalSettingsPtr()->Props() + // p_document->Creator() + const FBXDocParser::PropertyTable *import_props = doc.GetMetadataProperties(); + const FBXDocParser::PropertyPtr app_name = import_props->Get("Original|ApplicationName"); + const FBXDocParser::PropertyPtr app_vendor = import_props->Get("Original|ApplicationVendor"); + const FBXDocParser::PropertyPtr app_version = import_props->Get("Original|ApplicationVersion"); + // + if (app_name) { + const FBXDocParser::TypedProperty<std::string> *app_name_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_name); + if (app_name_string) { + print_verbose("FBX App Name: " + String(app_name_string->Value().c_str())); + } + } + + if (app_vendor) { + const FBXDocParser::TypedProperty<std::string> *app_vendor_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_vendor); + if (app_vendor_string) { + print_verbose("FBX App Vendor: " + String(app_vendor_string->Value().c_str())); + is_blender_fbx = app_vendor_string->Value().find("Blender") != std::string::npos; + } + } + + if (app_version) { + const FBXDocParser::TypedProperty<std::string> *app_version_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_version); + if (app_version_string) { + print_verbose("FBX App Version: " + String(app_version_string->Value().c_str())); + } + } + + if (is_blender_fbx) { + WARN_PRINT("Blender FBX files will not work properly with keyframes or skeletons until we make fixes stand by."); + } + + Node3D *spatial = _generate_scene(p_path, &doc, p_flags, p_bake_fps, 8); + // todo: move to document shutdown (will need to be validated after moving; this code has been validated already) + for (FBXDocParser::TokenPtr token : tokens) { + if (token) { + delete token; + token = nullptr; + } + } + + return spatial; + + } else { + print_error("Cannot import file: " + p_path + " version of file is unsupported, please re-export in your modelling package file version is: " + itos(doc.FBXVersion())); + } + } + + return memnew(Node3D); +} + +template <class T> +struct EditorSceneImporterAssetImportInterpolate { + T lerp(const T &a, const T &b, float c) const { + return a + (b - a) * c; + } + + T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) { + const float t2 = t * t; + const float t3 = t2 * t; + + return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + } + + T bezier(T start, T control_1, T control_2, T end, float t) { + /* Formula from Wikipedia article on Bezier curves. */ + const real_t omt = (1.0 - t); + const real_t omt2 = omt * omt; + const real_t omt3 = omt2 * omt; + const real_t t2 = t * t; + const real_t t3 = t2 * t; + + return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; + } +}; + +//thank you for existing, partial specialization +template <> +struct EditorSceneImporterAssetImportInterpolate<Quat> { + Quat lerp(const Quat &a, const Quat &b, 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, 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(Quat start, Quat control_1, Quat control_2, Quat end, 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 <class T> +T EditorSceneImporterFBX::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, + AssetImportAnimation::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++; + } + + EditorSceneImporterAssetImportInterpolate<T> interp; + + switch (p_interp) { + case AssetImportAnimation::INTERP_LINEAR: { + if (idx == -1) { + return p_values[0]; + } else if (idx >= p_times.size() - 1) { + return p_values[p_times.size() - 1]; + } + + 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 AssetImportAnimation::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 AssetImportAnimation::INTERP_CATMULLROMSPLINE: { + if (idx == -1) { + return p_values[1]; + } else if (idx >= p_times.size() - 1) { + return p_values[1 + p_times.size() - 1]; + } + + 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 AssetImportAnimation::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]; + } + + float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + T from = p_values[idx * 3 + 1]; + T c1 = from + p_values[idx * 3 + 2]; + T to = p_values[idx * 3 + 4]; + T c2 = to + p_values[idx * 3 + 3]; + + return interp.bezier(from, c1, c2, to, c); + + } break; + } + + ERR_FAIL_V(p_values[0]); +} + +Node3D *EditorSceneImporterFBX::_generate_scene( + const String &p_path, + const FBXDocParser::Document *p_document, + const uint32_t p_flags, + int p_bake_fps, + const int32_t p_max_bone_weights) { + ImportState state; + state.path = p_path; + state.animation_player = NULL; + + // create new root node for scene + Node3D *scene_root = memnew(Node3D); + state.root = memnew(Node3D); + state.root_owner = scene_root; // the real scene root... sorry compatibility code is painful... + + state.root->set_name("RootNode"); + scene_root->add_child(state.root); + state.root->set_owner(scene_root); + + state.fbx_root_node.instance(); + state.fbx_root_node->godot_node = state.root; + + // Size relative to cm. + const real_t fbx_unit_scale = p_document->GlobalSettingsPtr()->UnitScaleFactor(); + + print_verbose("FBX unit scale import value: " + rtos(fbx_unit_scale)); + // Set FBX file scale is relative to CM must be converted to M + state.scale = fbx_unit_scale / 100.0; + print_verbose("FBX unit scale is: " + rtos(state.scale)); + + // Enabled by default. + state.enable_material_import = true; + // Enabled by default. + state.enable_animation_import = true; + Ref<FBXNode> root_node; + root_node.instance(); + + // make sure fake noFBXDocParser::PropertyPtr ptrde always has a transform too ;) + Ref<PivotTransform> pivot_transform; + pivot_transform.instance(); + root_node->pivot_transform = pivot_transform; + root_node->node_name = "root node"; + root_node->current_node_id = 0; + root_node->godot_node = state.root; + + // cache this node onto the fbx_target map. + state.fbx_target_map.insert(0, root_node); + + // cache basic node information from FBX document + // grabs all FBX bones + BuildDocumentBones(Ref<FBXBone>(), state, p_document, 0L); + BuildDocumentNodes(Ref<PivotTransform>(), state, p_document, 0L, nullptr); + + // Build document skinning information + + // Algorithm is this: + // Get Deformer: object with "Skin" class. + // Deformer:: has link to Geometry:: (correct mesh for skin) + // Deformer:: has Source which is the SubDeformer:: (e.g. the Cluster) + // Notes at the end it configures the vertex weight mapping. + + for (uint64_t skin_id : p_document->GetSkinIDs()) { + // Validate the parser + FBXDocParser::LazyObject *lazy_skin = p_document->GetObject(skin_id); + ERR_CONTINUE_MSG(lazy_skin == nullptr, "invalid lazy object [serious parser bug]"); + + // Validate the parser + const FBXDocParser::Skin *skin = lazy_skin->Get<FBXDocParser::Skin>(); + ERR_CONTINUE_MSG(skin == nullptr, "invalid skin added to skin list [parser bug]"); + + const std::vector<const FBXDocParser::Connection *> source_to_destination = p_document->GetConnectionsBySourceSequenced(skin_id); + FBXDocParser::MeshGeometry *mesh = nullptr; + uint64_t mesh_id = 0; + + // Most likely only contains the mesh link for the skin + // The mesh geometry. + for (const FBXDocParser::Connection *con : source_to_destination) { + // do something + print_verbose("src: " + itos(con->src)); + FBXDocParser::Object *ob = con->DestinationObject(); + mesh = dynamic_cast<FBXDocParser::MeshGeometry *>(ob); + + if (mesh) { + mesh_id = mesh->ID(); + break; + } + } + + // Validate the mesh exists and was retrieved + ERR_CONTINUE_MSG(mesh_id == 0, "mesh id is invalid"); + const std::vector<const FBXDocParser::Cluster *> clusters = skin->Clusters(); + + // NOTE: this will ONLY work on skinned bones (it is by design.) + // A cluster is a skinned bone so SKINS won't contain unskinned bones so we need to pre-add all bones and parent them in a step beforehand. + for (const FBXDocParser::Cluster *cluster : clusters) { + ERR_CONTINUE_MSG(cluster == nullptr, "invalid bone cluster"); + const uint64_t deformer_id = cluster->ID(); + std::vector<const FBXDocParser::Connection *> connections = p_document->GetConnectionsByDestinationSequenced(deformer_id); + + // Weight data always has a node in the scene lets grab the limb's node in the scene :) (reverse set to true since it's the opposite way around) + const FBXDocParser::ModelLimbNode *limb_node = ProcessDOMConnection<FBXDocParser::ModelLimbNode>(p_document, deformer_id, true); + + ERR_CONTINUE_MSG(limb_node == nullptr, "unable to resolve model for skinned bone"); + + const uint64_t model_id = limb_node->ID(); + + // This will never happen, so if it does you know you fucked up. + ERR_CONTINUE_MSG(!state.fbx_bone_map.has(model_id), "missing LimbNode detected"); + + // new bone instance + Ref<FBXBone> bone_element = state.fbx_bone_map[model_id]; + + // + // Bone Weight Information Configuration + // + + // Cache Weight Information into bone for later usage if you want the raw data. + const std::vector<unsigned int> &indexes = cluster->GetIndices(); + const std::vector<float> &weights = cluster->GetWeights(); + Ref<FBXMeshData> mesh_vertex_data; + + // this data will pre-exist if vertex weight information is found + if (state.renderer_mesh_data.has(mesh_id)) { + mesh_vertex_data = state.renderer_mesh_data[mesh_id]; + } else { + mesh_vertex_data.instance(); + state.renderer_mesh_data.insert(mesh_id, mesh_vertex_data); + } + + mesh_vertex_data->armature_id = bone_element->armature_id; + mesh_vertex_data->valid_armature_id = true; + + //print_verbose("storing mesh vertex data for mesh to use later"); + ERR_CONTINUE_MSG(indexes.size() != weights.size(), "[doc] error mismatch between weight info"); + + for (size_t idx = 0; idx < indexes.size(); idx++) { + const size_t vertex_index = indexes[idx]; + const real_t influence_weight = weights[idx]; + + VertexWeightMapping &vm = mesh_vertex_data->vertex_weights[vertex_index]; + vm.weights.push_back(influence_weight); + vm.bones.push_back(0); // bone id is pushed on here during sanitization phase + vm.bones_ref.push_back(bone_element); + } + + for (const int *vertex_index = mesh_vertex_data->vertex_weights.next(nullptr); + vertex_index != nullptr; + vertex_index = mesh_vertex_data->vertex_weights.next(vertex_index)) { + VertexWeightMapping *vm = mesh_vertex_data->vertex_weights.getptr(*vertex_index); + const int influence_count = vm->weights.size(); + if (influence_count > mesh_vertex_data->max_weight_count) { + mesh_vertex_data->max_weight_count = influence_count; + mesh_vertex_data->valid_weight_count = true; + } + } + + if (mesh_vertex_data->max_weight_count > 4) { + if (mesh_vertex_data->max_weight_count > 8) { + ERR_PRINT("[doc] Serious: maximum bone influences is 8 in this branch."); + } + // Clamp to 8 bone vertex influences. + mesh_vertex_data->max_weight_count = 8; + print_verbose("[doc] Using 8 vertex bone influences configuration."); + } else { + mesh_vertex_data->max_weight_count = 4; + print_verbose("[doc] Using 4 vertex bone influences configuration."); + } + } + } + + // do we globally allow for import of materials + // (prevents overwrite of materials; so you can handle them explicitly) + if (state.enable_material_import) { + const std::vector<uint64_t> &materials = p_document->GetMaterialIDs(); + + for (uint64_t material_id : materials) { + FBXDocParser::LazyObject *lazy_material = p_document->GetObject(material_id); + FBXDocParser::Material *mat = (FBXDocParser::Material *)lazy_material->Get<FBXDocParser::Material>(); + ERR_CONTINUE_MSG(!mat, "Could not convert fbx material by id: " + itos(material_id)); + + Ref<FBXMaterial> material; + material.instance(); + material->set_imported_material(mat); + + Ref<StandardMaterial3D> godot_material = material->import_material(state); + + state.cached_materials.insert(material_id, godot_material); + } + } + + // build skin and skeleton information + print_verbose("[doc] Skeleton3D Bone count: " + itos(state.fbx_bone_map.size())); + + // Importing bones using document based method from FBX directly + // We do not use the assimp bone format to determine this information anymore. + if (state.fbx_bone_map.size() > 0) { + // We are using a single skeleton only method here + // this is because we really have no concept of skeletons in FBX + // their are bones in a scene but they have no specific armature + // we can detect armatures but the issue lies in the complexity + // we opted to merge the entire scene onto one skeleton for now + // if we need to change this we have an archive of the old code. + + // bind pose normally only has 1 per mesh but can have more than one + // this is the point of skins + // in FBX first bind pose is the master for the first skin + + // In order to handle the FBX skeleton we must also inverse any parent transforms on the bones + // just to rule out any parent node transforms in the bone data + // this is trivial to do and allows us to use the single skeleton method and merge them + // this means that the nodes from maya kLocators will be preserved as bones + // in the same rig without having to match this across skeletons and merge by detection + // we can just merge and undo any parent transforms + for (Map<uint64_t, Ref<FBXBone>>::Element *bone_element = state.fbx_bone_map.front(); bone_element; bone_element = bone_element->next()) { + Ref<FBXBone> bone = bone_element->value(); + Ref<FBXSkeleton> fbx_skeleton_inst; + + uint64_t armature_id = bone->armature_id; + if (state.skeleton_map.has(armature_id)) { + fbx_skeleton_inst = state.skeleton_map[armature_id]; + } else { + fbx_skeleton_inst.instance(); + state.skeleton_map.insert(armature_id, fbx_skeleton_inst); + } + + print_verbose("populating skeleton with bone: " + bone->bone_name); + + // // populate bone skeleton - since fbx has no DOM for the skeleton just a node. + // bone->bone_skeleton = fbx_skeleton_inst; + + // now populate bone on the armature node list + fbx_skeleton_inst->skeleton_bones.push_back(bone); + + CRASH_COND_MSG(!state.fbx_target_map.has(armature_id), "invalid armature [serious]"); + + Ref<FBXNode> node = state.fbx_target_map[armature_id]; + + CRASH_COND_MSG(node.is_null(), "invalid node [serious]"); + CRASH_COND_MSG(node->pivot_transform.is_null(), "invalid pivot transform [serious]"); + fbx_skeleton_inst->fbx_node = node; + + ERR_CONTINUE_MSG(fbx_skeleton_inst->fbx_node.is_null(), "invalid skeleton node [serious]"); + + // we need to have a valid armature id and the model configured for the bone to be assigned fully. + // happens once per skeleton + + if (state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) { + print_verbose("allocated fbx skeleton primary / armature node for the level: " + fbx_skeleton_inst->fbx_node->node_name); + } else if (!state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) { + print_error("bones are not mapped to an armature node for armature id: " + itos(armature_id) + " bone: " + bone->bone_name); + // this means bone will be removed and not used, which is safe actually and no skeleton will be created. + } + } + + // setup skeleton instances if required :) + for (Map<uint64_t, Ref<FBXSkeleton>>::Element *skeleton_node = state.skeleton_map.front(); skeleton_node; skeleton_node = skeleton_node->next()) { + Ref<FBXSkeleton> &skeleton = skeleton_node->value(); + skeleton->init_skeleton(state); + + ERR_CONTINUE_MSG(skeleton->fbx_node.is_null(), "invalid fbx target map, missing skeleton"); + } + + // This list is not populated + for (Map<uint64_t, Ref<FBXNode>>::Element *skin_mesh = state.MeshNodes.front(); skin_mesh; skin_mesh = skin_mesh->next()) { + } + } + + // build godot node tree + if (state.fbx_node_list.size() > 0) { + for (List<Ref<FBXNode>>::Element *node_element = state.fbx_node_list.front(); + node_element; + node_element = node_element->next()) { + Ref<FBXNode> fbx_node = node_element->get(); + EditorSceneImporterMeshNode3D *mesh_node = nullptr; + Ref<FBXMeshData> mesh_data_precached; + + // check for valid geometry + if (fbx_node->fbx_model == nullptr) { + print_error("[doc] fundamental flaw, submit bug immediately with full import log with verbose logging on"); + } else { + const std::vector<const FBXDocParser::Geometry *> &geometry = fbx_node->fbx_model->GetGeometry(); + for (const FBXDocParser::Geometry *mesh : geometry) { + print_verbose("[doc] [" + itos(mesh->ID()) + "] mesh: " + fbx_node->node_name); + + if (mesh == nullptr) + continue; + + const FBXDocParser::MeshGeometry *mesh_geometry = dynamic_cast<const FBXDocParser::MeshGeometry *>(mesh); + if (mesh_geometry) { + uint64_t mesh_id = mesh_geometry->ID(); + + // this data will pre-exist if vertex weight information is found + if (state.renderer_mesh_data.has(mesh_id)) { + mesh_data_precached = state.renderer_mesh_data[mesh_id]; + } else { + mesh_data_precached.instance(); + state.renderer_mesh_data.insert(mesh_id, mesh_data_precached); + } + + mesh_data_precached->mesh_node = fbx_node; + + // mesh node, mesh id + mesh_node = mesh_data_precached->create_fbx_mesh(state, mesh_geometry, fbx_node->fbx_model, (p_flags & IMPORT_USE_COMPRESSION) != 0); + if (!state.MeshNodes.has(mesh_id)) { + state.MeshNodes.insert(mesh_id, fbx_node); + } + } + + const FBXDocParser::ShapeGeometry *shape_geometry = dynamic_cast<const FBXDocParser::ShapeGeometry *>(mesh); + if (shape_geometry != nullptr) { + print_verbose("[doc] valid shape geometry converted"); + } + } + } + + Ref<FBXSkeleton> node_skeleton = fbx_node->skeleton_node; + + if (node_skeleton.is_valid()) { + Skeleton3D *skel = node_skeleton->skeleton; + fbx_node->godot_node = skel; + } else if (mesh_node == nullptr) { + fbx_node->godot_node = memnew(Node3D); + } else { + fbx_node->godot_node = mesh_node; + } + + fbx_node->godot_node->set_name(fbx_node->node_name); + + // assign parent if valid + if (fbx_node->fbx_parent.is_valid()) { + fbx_node->fbx_parent->godot_node->add_child(fbx_node->godot_node); + fbx_node->godot_node->set_owner(state.root_owner); + } + + // Node Transform debug, set local xform data. + fbx_node->godot_node->set_transform(get_unscaled_transform(fbx_node->pivot_transform->LocalTransform, state.scale)); + + // populate our mesh node reference + if (mesh_node != nullptr && mesh_data_precached.is_valid()) { + mesh_data_precached->godot_mesh_instance = mesh_node; + } + } + } + + for (Map<uint64_t, Ref<FBXMeshData>>::Element *mesh_data = state.renderer_mesh_data.front(); mesh_data; mesh_data = mesh_data->next()) { + const uint64_t mesh_id = mesh_data->key(); + Ref<FBXMeshData> mesh = mesh_data->value(); + + const FBXDocParser::MeshGeometry *mesh_geometry = p_document->GetObject(mesh_id)->Get<FBXDocParser::MeshGeometry>(); + + ERR_CONTINUE_MSG(mesh->mesh_node.is_null(), "invalid mesh allocation"); + + const FBXDocParser::Skin *mesh_skin = mesh_geometry->DeformerSkin(); + + if (!mesh_skin) { + continue; // safe to continue + } + + // + // Skin bone configuration + // + + // + // Get Mesh Node Xform only + // + // ERR_CONTINUE_MSG(!state.fbx_target_map.has(mesh_id), "invalid xform for the skin pose: " + itos(mesh_id)); + // Ref<FBXNode> mesh_node_xform_data = state.fbx_target_map[mesh_id]; + + if (!mesh_skin) { + continue; // not a deformer. + } + + if (mesh_skin->Clusters().size() == 0) { + continue; // possibly buggy mesh + } + + // Lookup skin or create it if it's not found. + Ref<Skin> skin; + if (!state.MeshSkins.has(mesh_id)) { + print_verbose("Created new skin"); + skin.instance(); + state.MeshSkins.insert(mesh_id, skin); + } else { + print_verbose("Grabbed skin"); + skin = state.MeshSkins[mesh_id]; + } + + for (const FBXDocParser::Cluster *cluster : mesh_skin->Clusters()) { + // node or bone this cluster targets (in theory will only be a bone target) + uint64_t skin_target_id = cluster->TargetNode()->ID(); + + print_verbose("adding cluster [" + itos(cluster->ID()) + "] " + String(cluster->Name().c_str()) + " for target: [" + itos(skin_target_id) + "] " + String(cluster->TargetNode()->Name().c_str())); + ERR_CONTINUE_MSG(!state.fbx_bone_map.has(skin_target_id), "no bone found by that ID? locator"); + + const Ref<FBXBone> bone = state.fbx_bone_map[skin_target_id]; + const Ref<FBXSkeleton> skeleton = bone->fbx_skeleton; + const Ref<FBXNode> skeleton_node = skeleton->fbx_node; + + skin->add_named_bind( + bone->bone_name, + get_unscaled_transform( + skeleton_node->pivot_transform->GlobalTransform.affine_inverse() * cluster->TransformLink().affine_inverse(), state.scale)); + } + + print_verbose("cluster name / id: " + String(mesh_skin->Name().c_str()) + " [" + itos(mesh_skin->ID()) + "]"); + print_verbose("skeleton has " + itos(state.fbx_bone_map.size()) + " binds"); + print_verbose("fbx skin has " + itos(mesh_skin->Clusters().size()) + " binds"); + } + + // mesh data iteration for populating skeleton mapping + for (Map<uint64_t, Ref<FBXMeshData>>::Element *mesh_data = state.renderer_mesh_data.front(); mesh_data; mesh_data = mesh_data->next()) { + Ref<FBXMeshData> mesh = mesh_data->value(); + const uint64_t mesh_id = mesh_data->key(); + EditorSceneImporterMeshNode3D *mesh_instance = mesh->godot_mesh_instance; + const int mesh_weights = mesh->max_weight_count; + Ref<FBXSkeleton> skeleton; + const bool valid_armature = mesh->valid_armature_id; + const uint64_t armature = mesh->armature_id; + + if (mesh_weights > 0) { + // this is a bug, it means the weights were found but the skeleton wasn't + ERR_CONTINUE_MSG(!valid_armature, "[doc] fbx armature is missing"); + } else { + continue; // safe to continue not a bug just a normal mesh + } + + if (state.skeleton_map.has(armature)) { + skeleton = state.skeleton_map[armature]; + print_verbose("[doc] armature mesh to skeleton mapping has been allocated"); + } else { + print_error("[doc] unable to find armature mapping"); + } + + ERR_CONTINUE_MSG(!mesh_instance, "[doc] invalid mesh mapping for skeleton assignment"); + ERR_CONTINUE_MSG(skeleton.is_null(), "[doc] unable to resolve the correct skeleton but we have weights!"); + + mesh_instance->set_skeleton_path(mesh_instance->get_path_to(skeleton->skeleton)); + print_verbose("[doc] allocated skeleton to mesh " + mesh_instance->get_name()); + + // do we have a mesh skin for this mesh + ERR_CONTINUE_MSG(!state.MeshSkins.has(mesh_id), "no skin found for mesh"); + + Ref<Skin> mesh_skin = state.MeshSkins[mesh_id]; + + ERR_CONTINUE_MSG(mesh_skin.is_null(), "invalid skin stored in map"); + print_verbose("[doc] allocated skin to mesh " + mesh_instance->get_name()); + mesh_instance->set_skin(mesh_skin); + } + + // build skin and skeleton information + print_verbose("[doc] Skeleton3D Bone count: " + itos(state.fbx_bone_map.size())); + const FBXDocParser::FileGlobalSettings *FBXSettings = p_document->GlobalSettingsPtr(); + + // Configure constraints + // NOTE: constraints won't be added quite yet, we don't have a real need for them *yet*. (they can be supported later on) + // const std::vector<uint64_t> fbx_constraints = p_document->GetConstraintStackIDs(); + + // get the animation FPS + float fps_setting = ImportUtils::get_fbx_fps(FBXSettings); + + // enable animation import, only if local animation is enabled + if (state.enable_animation_import && (p_flags & IMPORT_ANIMATION)) { + // document animation stack list - get by ID so we can unload any non used animation stack + const std::vector<uint64_t> animation_stack = p_document->GetAnimationStackIDs(); + + for (uint64_t anim_id : animation_stack) { + FBXDocParser::LazyObject *lazyObject = p_document->GetObject(anim_id); + const FBXDocParser::AnimationStack *stack = lazyObject->Get<FBXDocParser::AnimationStack>(); + + if (stack != nullptr) { + String animation_name = ImportUtils::FBXNodeToName(stack->Name()); + print_verbose("Valid animation stack has been found: " + animation_name); + // ReferenceTime is the same for some animations? + // LocalStop time is the start and end time + float r_start = CONVERT_FBX_TIME(stack->ReferenceStart()); + float r_stop = CONVERT_FBX_TIME(stack->ReferenceStop()); + float start_time = CONVERT_FBX_TIME(stack->LocalStart()); + float end_time = CONVERT_FBX_TIME(stack->LocalStop()); + float duration = end_time - start_time; + + print_verbose("r_start " + rtos(r_start) + ", r_stop " + rtos(r_stop)); + print_verbose("start_time" + rtos(start_time) + " end_time " + rtos(end_time)); + print_verbose("anim duration : " + rtos(duration)); + + // we can safely create the animation player + if (state.animation_player == nullptr) { + print_verbose("Creating animation player"); + state.animation_player = memnew(AnimationPlayer); + state.root->add_child(state.animation_player); + state.animation_player->set_owner(state.root_owner); + } + + Ref<Animation> animation; + animation.instance(); + animation->set_name(animation_name); + animation->set_length(duration); + + print_verbose("Animation length: " + rtos(animation->get_length()) + " seconds"); + + // i think assimp was duplicating things, this lets me know to just reference or ignore this to prevent duplicate information in tracks + // this would mean that we would be doing three times as much work per track if my theory is correct. + // this was not the case but this is a good sanity check for the animation handler from the document. + // it also lets us know if the FBX specification massively changes the animation system, in theory such a change would make this show + // an fbx specification error, so best keep it in + // the overhead is tiny. + Map<uint64_t, const FBXDocParser::AnimationCurve *> CheckForDuplication; + + const std::vector<const FBXDocParser::AnimationLayer *> &layers = stack->Layers(); + print_verbose("FBX Animation layers: " + itos(layers.size())); + for (const FBXDocParser::AnimationLayer *layer : layers) { + std::vector<const FBXDocParser::AnimationCurveNode *> node_list = layer->Nodes(); + print_verbose("Layer: " + ImportUtils::FBXNodeToName(layer->Name()) + ", " + " AnimCurveNode count " + itos(node_list.size())); + + // first thing to do here is that i need to first get the animcurvenode to a Vector3 + // we now need to put this into the track information for godot. + // to do this we need to know which track is what? + + // target id, [ track name, [time index, vector] ] + // new map needs to be [ track name, keyframe_data ] + Map<uint64_t, Map<StringName, FBXTrack>> AnimCurveNodes; + + // struct AnimTrack { + // // Animation track can be + // // visible, T, R, S + // Map<StringName, Map<uint64_t, Vector3> > animation_track; + // }; + + // Map<uint64_t, AnimTrack> AnimCurveNodes; + + // so really, what does this mean to make an animtion track. + // we need to know what object the curves are for. + // we need the target ID and the target name for the track reduction. + + FBXDocParser::Model::RotOrder quat_rotation_order = FBXDocParser::Model::RotOrder_EulerXYZ; + + // T:: R:: S:: Visible:: Custom:: + for (const FBXDocParser::AnimationCurveNode *curve_node : node_list) { + // when Curves() is called the curves are actually read, we could replace this with our own ProcessDomConnection code here if required. + // We may need to do this but ideally we use Curves + // note: when you call this there might be a delay in opening it + // uses mutable type to 'cache' the response until the AnimationCurveNode is cleaned up. + std::map<std::string, const FBXDocParser::AnimationCurve *> curves = curve_node->Curves(); + const FBXDocParser::Object *object = curve_node->Target(); + const FBXDocParser::Model *target = curve_node->TargetAsModel(); + if (target == nullptr) { + if (object != nullptr) { + print_error("[doc] warning failed to find a target Model for curve: " + String(object->Name().c_str())); + } else { + //print_error("[doc] failed to resolve object"); + continue; + } + + continue; + } else { + //print_verbose("[doc] applied rotation order: " + itos(target->RotationOrder())); + quat_rotation_order = target->RotationOrder(); + } + + uint64_t target_id = target->ID(); + String target_name = ImportUtils::FBXNodeToName(target->Name()); + + const FBXDocParser::PropertyTable *properties = curve_node->Props(); + bool got_x = false, got_y = false, got_z = false; + float offset_x = FBXDocParser::PropertyGet<float>(properties, "d|X", got_x); + float offset_y = FBXDocParser::PropertyGet<float>(properties, "d|Y", got_y); + float offset_z = FBXDocParser::PropertyGet<float>(properties, "d|Z", got_z); + + String curve_node_name = ImportUtils::FBXNodeToName(curve_node->Name()); + + // Reduce all curves for this node into a single container + // T, R, S is what we expect, although other tracks are possible + // like for example visibility tracks. + + // We are not ordered here, we don't care about ordering, this happens automagically by godot when we insert with the + // key time :), so order is unimportant because the insertion will happen at a time index + // good to know: we do not need a list of these in another format :) + //Map<String, Vector<const Assimp::FBX::AnimationCurve *> > unordered_track; + + // T + // R + // S + // Map[String, List<VECTOR>] + + // So this is a reduction of the animation curve nodes + // We build this as a lookup, this is essentially our 'animation track' + //AnimCurveNodes.insert(curve_node_name, Map<uint64_t, Vector3>()); + + // create the animation curve information with the target id + // so the point of this makes a track with the name "T" for example + // the target ID is also set here, this means we don't need to do anything extra when we are in the 'create all animation tracks' step + FBXTrack &keyframe_map = AnimCurveNodes[target_id][StringName(curve_node_name)]; + + if (got_x && got_y && got_z) { + Vector3 default_value = Vector3(offset_x, offset_y, offset_z); + keyframe_map.default_value = default_value; + keyframe_map.has_default = true; + //print_verbose("track name: " + curve_node_name); + //print_verbose("xyz default: " + default_value); + } + // target id, [ track name, [time index, vector] ] + // Map<uint64_t, Map<StringName, Map<uint64_t, Vector3> > > AnimCurveNodes; + + // we probably need the target id here. + // so map[uint64_t map]... + // Map<uint64_t, Vector3D> translation_keys, rotation_keys, scale_keys; + + // extra const required by C++11 colon/Range operator + // note: do not use C++17 syntax here for dicts. + // this is banned in Godot. + for (std::pair<const std::string, const FBXDocParser::AnimationCurve *> &kvp : curves) { + const String curve_element = ImportUtils::FBXNodeToName(kvp.first); + const FBXDocParser::AnimationCurve *curve = kvp.second; + String curve_name = ImportUtils::FBXNodeToName(curve->Name()); + uint64_t curve_id = curve->ID(); + + if (CheckForDuplication.has(curve_id)) { + print_error("(FBX spec changed?) We found a duplicate curve being used for an alternative node - report to godot issue tracker"); + } else { + CheckForDuplication.insert(curve_id, curve); + } + + // FBX has no name for AnimCurveNode::, most of the time, not seen any with valid name here. + const std::map<int64_t, float> &track_time = curve->GetValueTimeTrack(); + + if (track_time.size() > 0) { + for (std::pair<int64_t, float> keyframe : track_time) { + if (curve_element == "d|X") { + keyframe_map.keyframes[keyframe.first].x = keyframe.second; + } else if (curve_element == "d|Y") { + keyframe_map.keyframes[keyframe.first].y = keyframe.second; + } else if (curve_element == "d|Z") { + keyframe_map.keyframes[keyframe.first].z = keyframe.second; + } else { + //print_error("FBX Unsupported element: " + curve_element); + } + + //print_verbose("[" + itos(target_id) + "] Keyframe added: " + itos(keyframe_map.size())); + + //print_verbose("Keyframe t:" + rtos(animation_track_time) + " v: " + rtos(keyframe.second)); + } + } + } + } + + // Map<uint64_t, Map<StringName, Map<uint64_t, Vector3> > > AnimCurveNodes; + // add this animation track here + + // target id, [ track name, [time index, vector] ] + //std::map<uint64_t, std::map<StringName, FBXTrack > > AnimCurveNodes; + for (Map<uint64_t, Map<StringName, FBXTrack>>::Element *track = AnimCurveNodes.front(); track; track = track->next()) { + // 5 tracks + // current track index + // track count is 5 + // track count is 5. + // next track id is 5. + const uint64_t target_id = track->key(); + int track_idx = animation->add_track(Animation::TYPE_TRANSFORM); + + // animation->track_set_path(track_idx, node_path); + // animation->track_set_path(track_idx, node_path); + Ref<FBXBone> bone; + + // note we must not run the below code if the entry doesn't exist, it will create dummy entries which is very bad. + // remember that state.fbx_bone_map[target_id] will create a new entry EVEN if you only read. + // this would break node animation targets, so if you change this be warned. :) + if (state.fbx_bone_map.has(target_id)) { + bone = state.fbx_bone_map[target_id]; + } + + Transform target_transform; + + if (state.fbx_target_map.has(target_id)) { + Ref<FBXNode> node_ref = state.fbx_target_map[target_id]; + target_transform = node_ref->pivot_transform->GlobalTransform; + //print_verbose("[doc] allocated animation node transform"); + } + + //int size_targets = state.fbx_target_map.size(); + //print_verbose("Target ID map: " + itos(size_targets)); + //print_verbose("[doc] debug bone map size: " + itos(state.fbx_bone_map.size())); + + // if this is a skeleton mapped track we can just set the path for the track. + // todo: implement node paths here at some + if (state.fbx_bone_map.size() > 0 && state.fbx_bone_map.has(target_id)) { + if (bone->fbx_skeleton.is_valid() && bone.is_valid()) { + Ref<FBXSkeleton> fbx_skeleton = bone->fbx_skeleton; + String bone_path = state.root->get_path_to(fbx_skeleton->skeleton); + bone_path += ":" + fbx_skeleton->skeleton->get_bone_name(bone->godot_bone_id); + print_verbose("[doc] track bone path: " + bone_path); + NodePath path = bone_path; + animation->track_set_path(track_idx, path); + } + } else if (state.fbx_target_map.has(target_id)) { + //print_verbose("[doc] we have a valid target for a node animation"); + Ref<FBXNode> target_node = state.fbx_target_map[target_id]; + if (target_node.is_valid() && target_node->godot_node != nullptr) { + String node_path = state.root->get_path_to(target_node->godot_node); + NodePath path = node_path; + animation->track_set_path(track_idx, path); + //print_verbose("[doc] node animation path: " + node_path); + } + } else { + // note: this could actually be unsafe this means we should be careful about continuing here, if we see bizzare effects later we should disable this. + // I am not sure if this is unsafe or not, testing will tell us this. + print_error("[doc] invalid fbx target detected for this track"); + continue; + } + + // everything in FBX and Maya is a node therefore if this happens something is seriously broken. + if (!state.fbx_target_map.has(target_id)) { + print_error("unable to resolve this to an FBX object."); + continue; + } + + Ref<FBXNode> target_node = state.fbx_target_map[target_id]; + const FBXDocParser::Model *model = target_node->fbx_model; + const FBXDocParser::PropertyTable *props = model->Props(); + + Map<StringName, FBXTrack> &track_data = track->value(); + FBXTrack &translation_keys = track_data[StringName("T")]; + FBXTrack &rotation_keys = track_data[StringName("R")]; + FBXTrack &scale_keys = track_data[StringName("S")]; + + double increment = 1.0f / fps_setting; + double time = 0.0f; + + bool last = false; + + Vector<Vector3> pos_values; + Vector<float> pos_times; + Vector<Vector3> scale_values; + Vector<float> scale_times; + Vector<Quat> rot_values; + Vector<float> rot_times; + + double max_duration = 0; + double anim_length = animation->get_length(); + + for (std::pair<int64_t, Vector3> position_key : translation_keys.keyframes) { + pos_values.push_back(position_key.second * state.scale); + double animation_track_time = CONVERT_FBX_TIME(position_key.first); + + if (animation_track_time > max_duration) { + max_duration = animation_track_time; + } + + //print_verbose("pos keyframe: t:" + rtos(animation_track_time) + " value " + position_key.second); + pos_times.push_back(animation_track_time); + } + + for (std::pair<int64_t, Vector3> scale_key : scale_keys.keyframes) { + scale_values.push_back(scale_key.second); + double animation_track_time = CONVERT_FBX_TIME(scale_key.first); + + if (animation_track_time > max_duration) { + max_duration = animation_track_time; + } + //print_verbose("scale keyframe t:" + rtos(animation_track_time)); + scale_times.push_back(animation_track_time); + } + + // + // Pre and Post keyframe rotation handler + // -- Required because Maya and Autodesk <3 the pain when it comes to implementing animation code! enjoy <3 + + bool got_pre = false; + bool got_post = false; + + Quat post_rotation; + Quat pre_rotation; + + // Rotation matrix + const Vector3 &PreRotation = FBXDocParser::PropertyGet<Vector3>(props, "PreRotation", got_pre); + const Vector3 &PostRotation = FBXDocParser::PropertyGet<Vector3>(props, "PostRotation", got_post); + + FBXDocParser::Model::RotOrder rot_order = model->RotationOrder(); + if (got_pre) { + pre_rotation = ImportUtils::EulerToQuaternion(rot_order, ImportUtils::deg2rad(PreRotation)); + } + if (got_post) { + post_rotation = ImportUtils::EulerToQuaternion(rot_order, ImportUtils::deg2rad(PostRotation)); + } + + Quat lastQuat = Quat(); + + for (std::pair<int64_t, Vector3> rotation_key : rotation_keys.keyframes) { + double animation_track_time = CONVERT_FBX_TIME(rotation_key.first); + + //print_verbose("euler rotation key: " + rotation_key.second); + Quat rot_key_value = ImportUtils::EulerToQuaternion(quat_rotation_order, ImportUtils::deg2rad(rotation_key.second)); + + if (lastQuat != Quat() && rot_key_value.dot(lastQuat) < 0) { + rot_key_value.x = -rot_key_value.x; + rot_key_value.y = -rot_key_value.y; + rot_key_value.z = -rot_key_value.z; + rot_key_value.w = -rot_key_value.w; + } + // pre_post rotation possibly could fix orientation + Quat final_rotation = pre_rotation * rot_key_value * post_rotation; + + lastQuat = final_rotation; + + if (animation_track_time > max_duration) { + max_duration = animation_track_time; + } + + rot_values.push_back(final_rotation); + rot_times.push_back(animation_track_time); + } + + bool valid_rest = false; + Transform bone_rest; + int skeleton_bone = -1; + if (state.fbx_bone_map.has(target_id)) { + if (bone.is_valid() && bone->fbx_skeleton.is_valid()) { + skeleton_bone = bone->godot_bone_id; + if (skeleton_bone >= 0) { + bone_rest = bone->fbx_skeleton->skeleton->get_bone_rest(skeleton_bone); + valid_rest = true; + } + } + + if (!valid_rest) { + print_verbose("invalid rest!"); + } + } + + const Vector3 def_pos = translation_keys.has_default ? (translation_keys.default_value * state.scale) : bone_rest.origin; + const Quat def_rot = rotation_keys.has_default ? ImportUtils::EulerToQuaternion(quat_rotation_order, ImportUtils::deg2rad(rotation_keys.default_value)) : bone_rest.basis.get_rotation_quat(); + const Vector3 def_scale = scale_keys.has_default ? scale_keys.default_value : bone_rest.basis.get_scale(); + print_verbose("track defaults: p(" + def_pos + ") s(" + def_scale + ") r(" + def_rot + ")"); + + while (true) { + Vector3 pos = def_pos; + Quat rot = def_rot; + Vector3 scale = def_scale; + + if (pos_values.size()) { + pos = _interpolate_track<Vector3>(pos_times, pos_values, time, + AssetImportAnimation::INTERP_LINEAR); + } + + if (rot_values.size()) { + rot = _interpolate_track<Quat>(rot_times, rot_values, time, + AssetImportAnimation::INTERP_LINEAR); + } + + if (scale_values.size()) { + scale = _interpolate_track<Vector3>(scale_times, scale_values, time, + AssetImportAnimation::INTERP_LINEAR); + } + + // node animations must also include pivots + if (skeleton_bone >= 0) { + Transform xform = Transform(); + xform.basis.set_quat_scale(rot, scale); + xform.origin = pos; + const Transform t = bone_rest.affine_inverse() * xform; + + // populate this again + rot = t.basis.get_rotation_quat(); + rot.normalize(); + scale = t.basis.get_scale(); + pos = t.origin; + } + + animation->transform_track_insert_key(track_idx, time, pos, rot, scale); + + if (last) { + break; + } + + time += increment; + if (time > anim_length) { + last = true; + time = anim_length; + break; + } + } + } + } + state.animation_player->add_animation(animation_name, animation); + } + } + + // AnimStack elements contain start stop time and name of animation + // AnimLayer is the current active layer of the animation (multiple layers can be active we only support 1) + // AnimCurveNode has a OP link back to the model which is the real node. + // AnimCurveNode has a direct link to AnimationCurve (of which it may have more than one) + + // Store animation stack in list + // iterate over all AnimStacks like the cache node algorithm recursively + // this can then be used with ProcessDomConnection<> to link from + // AnimStack:: <-- (OO) --> AnimLayer:: <-- (OO) --> AnimCurveNode:: (which can OP resolve) to Model:: + } + + // + // Cleanup operations - explicit to prevent errors on shutdown - found that ref to ref does behave badly sometimes. + // + + state.renderer_mesh_data.clear(); + state.MeshSkins.clear(); + state.fbx_target_map.clear(); + state.fbx_node_list.clear(); + + for (Map<uint64_t, Ref<FBXBone>>::Element *element = state.fbx_bone_map.front(); element; element = element->next()) { + Ref<FBXBone> bone = element->value(); + bone->parent_bone.unref(); + bone->node.unref(); + bone->fbx_skeleton.unref(); + } + + for (Map<uint64_t, Ref<FBXSkeleton>>::Element *element = state.skeleton_map.front(); element; element = element->next()) { + Ref<FBXSkeleton> skel = element->value(); + skel->fbx_node.unref(); + skel->skeleton_bones.clear(); + } + + state.fbx_bone_map.clear(); + state.skeleton_map.clear(); + state.fbx_root_node.unref(); + + return scene_root; +} + +void EditorSceneImporterFBX::BuildDocumentBones(Ref<FBXBone> p_parent_bone, + ImportState &state, const FBXDocParser::Document *p_doc, + uint64_t p_id) { + const std::vector<const FBXDocParser::Connection *> &conns = p_doc->GetConnectionsByDestinationSequenced(p_id, "Model"); + // FBX can do an join like this + // Model -> SubDeformer (bone) -> Deformer (skin pose) + // This is important because we need to somehow link skin back to bone id in skeleton :) + // The rules are: + // A subdeformer will exist if 'limbnode' class tag present + // The subdeformer will not necessarily have a deformer as joints do not have one + for (const FBXDocParser::Connection *con : conns) { + // goto: bone creation + //print_verbose("con: " + String(con->PropertyName().c_str())); + + // ignore object-property links we want the object to object links nothing else + if (con->PropertyName().length()) { + continue; + } + + // convert connection source object into Object base class + const FBXDocParser::Object *const object = con->SourceObject(); + + if (nullptr == object) { + print_verbose("failed to convert source object for Model link"); + continue; + } + + // FBX Model::Cube, Model::Bone001, etc elements + // This detects if we can cast the object into this model structure. + const FBXDocParser::Model *const model = dynamic_cast<const FBXDocParser::Model *>(object); + + // declare our bone element reference (invalid, unless we create a bone in this step) + // this lets us pass valid armature information into children objects and this is why we moved this up here + // previously this was created .instanced() on the same line. + Ref<FBXBone> bone_element; + + if (model != nullptr) { + // model marked with limb node / casted. + const FBXDocParser::ModelLimbNode *const limb_node = dynamic_cast<const FBXDocParser::ModelLimbNode *>(model); + if (limb_node != nullptr) { + // Write bone into bone list for FBX + + ERR_FAIL_COND_MSG(state.fbx_bone_map.has(limb_node->ID()), "[serious] duplicate LimbNode detected"); + + bool parent_is_bone = state.fbx_bone_map.find(p_id); + bone_element.instance(); + + // used to build the bone hierarchy in the skeleton + bone_element->parent_bone_id = parent_is_bone ? p_id : 0; + bone_element->valid_parent = parent_is_bone; + bone_element->limb_node = limb_node; + + // parent is a node and this is the first bone + if (!parent_is_bone) { + uint64_t armature_id = p_id; + bone_element->valid_armature_id = true; + bone_element->armature_id = armature_id; + print_verbose("[doc] valid armature has been configured for first child: " + itos(armature_id)); + } else if (p_parent_bone.is_valid()) { + if (p_parent_bone->valid_armature_id) { + bone_element->valid_armature_id = true; + bone_element->armature_id = p_parent_bone->armature_id; + print_verbose("[doc] bone has valid armature id:" + itos(bone_element->armature_id)); + } else { + print_error("[doc] unassigned armature id: " + String(limb_node->Name().c_str())); + } + } else { + print_error("[doc] error is this a bone? " + String(limb_node->Name().c_str())); + } + + if (!parent_is_bone) { + print_verbose("[doc] Root bone: " + bone_element->bone_name); + } + + uint64_t limb_id = limb_node->ID(); + bone_element->bone_id = limb_id; + bone_element->bone_name = ImportUtils::FBXNodeToName(model->Name()); + bone_element->parent_bone = p_parent_bone; + + // insert limb by ID into list. + state.fbx_bone_map.insert(limb_node->ID(), bone_element); + } + + // recursion call - child nodes + BuildDocumentBones(bone_element, state, p_doc, model->ID()); + } + } +} + +void EditorSceneImporterFBX::BuildDocumentNodes( + Ref<PivotTransform> parent_transform, + ImportState &state, + const FBXDocParser::Document *p_doc, + uint64_t id, + Ref<FBXNode> parent_node) { + // tree + // here we get the node 0 on the root by default + const std::vector<const FBXDocParser::Connection *> &conns = p_doc->GetConnectionsByDestinationSequenced(id, "Model"); + + // branch + for (const FBXDocParser::Connection *con : conns) { + // ignore object-property links + if (con->PropertyName().length()) { + // really important we document why this is ignored. + print_verbose("ignoring property link - no docs on why this is ignored"); + continue; + } + + // convert connection source object into Object base class + // Source objects can exist with 'null connections' this means that we only for sure know the source exists. + const FBXDocParser::Object *const source_object = con->SourceObject(); + + if (nullptr == source_object) { + print_verbose("failed to convert source object for Model link"); + continue; + } + + // FBX Model::Cube, Model::Bone001, etc elements + // This detects if we can cast the object into this model structure. + const FBXDocParser::Model *const model = dynamic_cast<const FBXDocParser::Model *>(source_object); + // model is the current node + if (nullptr != model) { + uint64_t current_node_id = model->ID(); + + Ref<FBXNode> new_node; + new_node.instance(); + new_node->current_node_id = current_node_id; + new_node->node_name = ImportUtils::FBXNodeToName(model->Name()); + + Ref<PivotTransform> fbx_transform; + fbx_transform.instance(); + fbx_transform->set_parent(parent_transform); + fbx_transform->set_model(model); + fbx_transform->debug_pivot_xform("name: " + new_node->node_name); + fbx_transform->Execute(); + + new_node->set_pivot_transform(fbx_transform); + + // check if this node is a bone + if (state.fbx_bone_map.has(current_node_id)) { + Ref<FBXBone> bone = state.fbx_bone_map[current_node_id]; + if (bone.is_valid()) { + bone->set_node(new_node); + print_verbose("allocated bone data: " + bone->bone_name); + } + } + + // set the model, we can't just assign this safely + new_node->set_model(model); + + if (parent_node.is_valid()) { + new_node->set_parent(parent_node); + } else { + new_node->set_parent(state.fbx_root_node); + } + + CRASH_COND_MSG(new_node->pivot_transform.is_null(), "invalid fbx target map pivot transform [serious]"); + + // populate lookup tables with references + // [fbx_node_id, fbx_node] + + state.fbx_node_list.push_back(new_node); + if (!state.fbx_target_map.has(new_node->current_node_id)) { + state.fbx_target_map[new_node->current_node_id] = new_node; + } + + // print node name + print_verbose("[doc] new node " + new_node->node_name); + + // sub branches + BuildDocumentNodes(new_node->pivot_transform, state, p_doc, current_node_id, new_node); + } + } +} diff --git a/modules/assimp/editor_scene_importer_assimp.h b/modules/fbx/editor_scene_importer_fbx.h index 80bba6ad66..ff1d243ede 100644 --- a/modules/assimp/editor_scene_importer_assimp.h +++ b/modules/fbx/editor_scene_importer_fbx.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* editor_scene_importer_assimp.h */ +/* editor_scene_importer_fbx.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,13 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef EDITOR_SCENE_IMPORTER_ASSIMP_H -#define EDITOR_SCENE_IMPORTER_ASSIMP_H +#ifndef EDITOR_SCENE_IMPORTER_FBX_H +#define EDITOR_SCENE_IMPORTER_FBX_H #ifdef TOOLS_ENABLED + +#include "data/import_state.h" +#include "tools/import_utils.h" + #include "core/core_bind.h" #include "core/io/resource_importer.h" +#include "core/string/ustring.h" +#include "core/templates/local_vector.h" #include "core/templates/vector.h" +#include "core/variant/dictionary.h" #include "editor/import/resource_importer_scene.h" #include "editor/project_settings_editor.h" #include "scene/3d/mesh_instance_3d.h" @@ -44,35 +51,16 @@ #include "scene/resources/animation.h" #include "scene/resources/surface_tool.h" -#include <assimp/matrix4x4.h> -#include <assimp/scene.h> -#include <assimp/types.h> -#include <assimp/DefaultLogger.hpp> -#include <assimp/LogStream.hpp> -#include <assimp/Logger.hpp> -#include <map> - -#include "import_state.h" -#include "import_utils.h" +#include "fbx_parser/FBXDocument.h" +#include "fbx_parser/FBXImportSettings.h" +#include "fbx_parser/FBXMeshGeometry.h" +#include "fbx_parser/FBXUtil.h" -using namespace AssimpImporter; - -class AssimpStream : public Assimp::LogStream { -public: - // Constructor - AssimpStream() {} - - // Destructor - ~AssimpStream() {} - // Write something using your own functionality - void write(const char *message) { - print_verbose(String("Open Asset Import: ") + String(message).strip_edges()); - } -}; +#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL -class EditorSceneImporterAssimp : public EditorSceneImporter { +class EditorSceneImporterFBX : public EditorSceneImporter { private: - GDCLASS(EditorSceneImporterAssimp, EditorSceneImporter); + GDCLASS(EditorSceneImporterFBX, EditorSceneImporter); struct AssetImportAnimation { enum Interpolation { @@ -83,67 +71,63 @@ private: }; }; - struct BoneInfo { - uint32_t bone; - float weight; - }; + // ------------------------------------------------------------------------------------------------ + template <typename T> + const T *ProcessDOMConnection( + const FBXDocParser::Document *doc, + uint64_t current_element, + bool reverse_lookup = false) { + const std::vector<const FBXDocParser::Connection *> &conns = reverse_lookup ? doc->GetConnectionsByDestinationSequenced(current_element) : doc->GetConnectionsBySourceSequenced(current_element); + //print_verbose("[doc] looking for " + String(element_to_find)); + // using the temp pattern here so we can debug before it returns + // in some cases we return too early, with 'deformer object base class' in wrong place + // in assimp this means we can accidentally return too early... + const T *return_obj = nullptr; + + for (const FBXDocParser::Connection *con : conns) { + const FBXDocParser::Object *source_object = con->SourceObject(); + const FBXDocParser::Object *dest_object = con->DestinationObject(); + if (source_object && dest_object != nullptr) { + //print_verbose("[doc] connection name: " + String(source_object->Name().c_str()) + ", dest: " + String(dest_object->Name().c_str())); + const T *temp = dynamic_cast<const T *>(reverse_lookup ? source_object : dest_object); + if (temp) { + return_obj = temp; + } + } + } + + if (return_obj != nullptr) { + //print_verbose("[doc] returned valid element"); + //print_verbose("Found object for bone"); + return return_obj; + } + + // safe to return nothing, need to use nullptr here as nullptr is used internally for FBX document. + return nullptr; + } + + void BuildDocumentBones(Ref<FBXBone> p_parent_bone, + ImportState &state, const FBXDocParser::Document *p_doc, + uint64_t p_id); - Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, - const aiNode *assimp_node, Ref<Skin> &skin, - Skeleton3D *&skeleton_assigned); - - // simple object creation functions - Node3D *create_light(ImportState &state, - const String &node_name, - Transform &look_at_transform); - Node3D *create_camera( - ImportState &state, - const String &node_name, - Transform &look_at_transform); - // non recursive - linear so must not use recursive arguments - MeshInstance3D *create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *active_node, Transform node_transform); - // recursive node generator - void _generate_node(ImportState &state, const aiNode *assimp_node); - void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int track_id, - int anim_fps, Ref<Animation> animation, float ticks_per_second, - Skeleton3D *skeleton, const NodePath &node_path, - const String &node_name, aiBone *track_bone); - - void _import_animation(ImportState &state, int p_animation_index, int p_bake_fps); - Node *get_node_by_name(ImportState &state, String name); - aiBone *get_bone_from_stack(ImportState &state, aiString name); - Node3D *_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights); + void BuildDocumentNodes(Ref<PivotTransform> parent_transform, ImportState &state, const FBXDocParser::Document *doc, uint64_t id, Ref<FBXNode> fbx_parent); + + Node3D *_generate_scene(const String &p_path, const FBXDocParser::Document *p_document, + const uint32_t p_flags, + int p_bake_fps, const int32_t p_max_bone_weights); template <class T> T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp); void _register_project_setting_import(const String generic, const String import_setting_string, const Vector<String> &exts, List<String> *r_extensions, const bool p_enabled) const; - struct ImportFormat { - Vector<String> extensions; - bool is_default; - }; - -protected: - static void _bind_methods(); - public: - EditorSceneImporterAssimp() { - Assimp::DefaultLogger::create("", Assimp::Logger::VERBOSE); - unsigned int severity = Assimp::Logger::Info | Assimp::Logger::Err | Assimp::Logger::Warn; - Assimp::DefaultLogger::get()->attachStream(new AssimpStream(), severity); - } - ~EditorSceneImporterAssimp() { - Assimp::DefaultLogger::kill(); - } + EditorSceneImporterFBX() {} + ~EditorSceneImporterFBX() {} virtual void get_extensions(List<String> *r_extensions) const override; virtual uint32_t get_import_flags() const override; - virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = nullptr) override; - Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path); - - static void RegenerateBoneStack(ImportState &state); - - void RegenerateBoneStack(ImportState &state, aiMesh *mesh); + virtual Node3D *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = NULL) override; }; -#endif -#endif + +#endif // TOOLS_ENABLED +#endif // EDITOR_SCENE_IMPORTER_FBX_H diff --git a/modules/fbx/fbx_parser/ByteSwapper.h b/modules/fbx/fbx_parser/ByteSwapper.h new file mode 100644 index 0000000000..0a46a7df26 --- /dev/null +++ b/modules/fbx/fbx_parser/ByteSwapper.h @@ -0,0 +1,282 @@ +/*************************************************************************/ +/* ByteSwapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file Helper class tp perform various byte oder swappings + (e.g. little to big endian) */ +#ifndef BYTE_SWAPPER_H +#define BYTE_SWAPPER_H + +#include <stdint.h> +#include <algorithm> +#include <locale> + +namespace FBXDocParser { +// -------------------------------------------------------------------------------------- +/** Defines some useful byte order swap routines. + * + * This is required to read big-endian model formats on little-endian machines, + * and vice versa. Direct use of this class is DEPRECATED. Use #StreamReader instead. */ +// -------------------------------------------------------------------------------------- +class ByteSwap { + ByteSwap() {} + +public: + // ---------------------------------------------------------------------- + /** Swap two bytes of data + * @param[inout] _szOut A void* to save the reintcasts for the caller. */ + static inline void Swap2(void *_szOut) { + uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut); + std::swap(szOut[0], szOut[1]); + } + + // ---------------------------------------------------------------------- + /** Swap four bytes of data + * @param[inout] _szOut A void* to save the reintcasts for the caller. */ + static inline void Swap4(void *_szOut) { + uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut); + std::swap(szOut[0], szOut[3]); + std::swap(szOut[1], szOut[2]); + } + + // ---------------------------------------------------------------------- + /** Swap eight bytes of data + * @param[inout] _szOut A void* to save the reintcasts for the caller. */ + static inline void Swap8(void *_szOut) { + uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut); + std::swap(szOut[0], szOut[7]); + std::swap(szOut[1], szOut[6]); + std::swap(szOut[2], szOut[5]); + std::swap(szOut[3], szOut[4]); + } + + // ---------------------------------------------------------------------- + /** ByteSwap a float. Not a joke. + * @param[inout] fOut ehm. .. */ + static inline void Swap(float *fOut) { + Swap4(fOut); + } + + // ---------------------------------------------------------------------- + /** ByteSwap a double. Not a joke. + * @param[inout] fOut ehm. .. */ + static inline void Swap(double *fOut) { + Swap8(fOut); + } + + // ---------------------------------------------------------------------- + /** ByteSwap an int16t. Not a joke. + * @param[inout] fOut ehm. .. */ + static inline void Swap(int16_t *fOut) { + Swap2(fOut); + } + + static inline void Swap(uint16_t *fOut) { + Swap2(fOut); + } + + // ---------------------------------------------------------------------- + /** ByteSwap an int32t. Not a joke. + * @param[inout] fOut ehm. .. */ + static inline void Swap(int32_t *fOut) { + Swap4(fOut); + } + + static inline void Swap(uint32_t *fOut) { + Swap4(fOut); + } + + // ---------------------------------------------------------------------- + /** ByteSwap an int64t. Not a joke. + * @param[inout] fOut ehm. .. */ + static inline void Swap(int64_t *fOut) { + Swap8(fOut); + } + + static inline void Swap(uint64_t *fOut) { + Swap8(fOut); + } + + // ---------------------------------------------------------------------- + //! Templatized ByteSwap + //! \returns param tOut as swapped + template <typename Type> + static inline Type Swapped(Type tOut) { + return _swapper<Type, sizeof(Type)>()(tOut); + } + +private: + template <typename T, size_t size> + struct _swapper; +}; + +template <typename T> +struct ByteSwap::_swapper<T, 2> { + T operator()(T tOut) { + Swap2(&tOut); + return tOut; + } +}; + +template <typename T> +struct ByteSwap::_swapper<T, 4> { + T operator()(T tOut) { + Swap4(&tOut); + return tOut; + } +}; + +template <typename T> +struct ByteSwap::_swapper<T, 8> { + T operator()(T tOut) { + Swap8(&tOut); + return tOut; + } +}; + +// -------------------------------------------------------------------------------------- +// ByteSwap macros for BigEndian/LittleEndian support +// -------------------------------------------------------------------------------------- +#if (defined AI_BUILD_BIG_ENDIAN) +#define AI_LE(t) (t) +#define AI_BE(t) ByteSwap::Swapped(t) +#define AI_LSWAP2(p) +#define AI_LSWAP4(p) +#define AI_LSWAP8(p) +#define AI_LSWAP2P(p) +#define AI_LSWAP4P(p) +#define AI_LSWAP8P(p) +#define LE_NCONST const +#define AI_SWAP2(p) ByteSwap::Swap2(&(p)) +#define AI_SWAP4(p) ByteSwap::Swap4(&(p)) +#define AI_SWAP8(p) ByteSwap::Swap8(&(p)) +#define AI_SWAP2P(p) ByteSwap::Swap2((p)) +#define AI_SWAP4P(p) ByteSwap::Swap4((p)) +#define AI_SWAP8P(p) ByteSwap::Swap8((p)) +#define BE_NCONST +#else +#define AI_BE(t) (t) +#define AI_LE(t) ByteSwap::Swapped(t) +#define AI_SWAP2(p) +#define AI_SWAP4(p) +#define AI_SWAP8(p) +#define AI_SWAP2P(p) +#define AI_SWAP4P(p) +#define AI_SWAP8P(p) +#define BE_NCONST const +#define AI_LSWAP2(p) ByteSwap::Swap2(&(p)) +#define AI_LSWAP4(p) ByteSwap::Swap4(&(p)) +#define AI_LSWAP8(p) ByteSwap::Swap8(&(p)) +#define AI_LSWAP2P(p) ByteSwap::Swap2((p)) +#define AI_LSWAP4P(p) ByteSwap::Swap4((p)) +#define AI_LSWAP8P(p) ByteSwap::Swap8((p)) +#define LE_NCONST +#endif + +namespace Intern { + +// -------------------------------------------------------------------------------------------- +template <typename T, bool doit> +struct ByteSwapper { + void operator()(T *inout) { + ByteSwap::Swap(inout); + } +}; + +template <typename T> +struct ByteSwapper<T, false> { + void operator()(T *) { + } +}; + +// -------------------------------------------------------------------------------------------- +template <bool SwapEndianess, typename T, bool RuntimeSwitch> +struct Getter { + void operator()(T *inout, bool le) { + le = !le; + if (le) { + ByteSwapper<T, (sizeof(T) > 1 ? true : false)>()(inout); + } else + ByteSwapper<T, false>()(inout); + } +}; + +template <bool SwapEndianess, typename T> +struct Getter<SwapEndianess, T, false> { + void operator()(T *inout, bool /*le*/) { + // static branch + ByteSwapper<T, (SwapEndianess && sizeof(T) > 1)>()(inout); + } +}; +} // namespace Intern +} // namespace FBXDocParser + +#endif // BYTE_SWAPPER_H diff --git a/modules/fbx/fbx_parser/CREDITS b/modules/fbx/fbx_parser/CREDITS new file mode 100644 index 0000000000..62b449614e --- /dev/null +++ b/modules/fbx/fbx_parser/CREDITS @@ -0,0 +1,183 @@ +=============================================================== +Open Asset Import Library (Assimp) +Developers and Contributors +=============================================================== + +The following is a non-exhaustive list of all constributors over the years. +If you think your name should be listed here, drop us a line and we'll add you. + +- Alexander Gessler, +3DS-, BLEND-, ASE-, DXF-, HMP-, MDL-, MD2-, MD3-, MD5-, MDC-, NFF-, PLY-, STL-, RAW-, OFF-, MS3D-, Q3D- and LWO-Loader, Assimp-Viewer, assimp-cmd, -noboost, Website (Design). + +- Thomas Schulze, +X-, Collada-, BVH-Loader, Postprocessing framework. Data structure & Interface design, documentation. + +- Kim Kulling, +Obj-, Q3BSD-, OpenGEX-Loader, Logging system, CMake-build-environment, Linux-build, Website ( Admin ), Coverity ( Admin ), Glitter ( Admin ). + +- R.Schmidt, +Linux build, eclipse support. + +- Matthias Gubisch, +Assimp.net +Visual Studio 9 support, bugfixes. + +- Mark Sibly +B3D-Loader, Assimp testing + +- Jonathan Klein +Ogre Loader, VC2010 fixes and CMake fixes. + +- Sebastian Hempel, +PyAssimp (first version) +Compile-Bugfixes for mingw, add environment for static library support in make. + +- Jonathan Pokrass +Supplied a bugfix concerning the scaling in the md3 loader. + +- Andrew Galante, +Submitted patches to make Assimp compile with GCC-4, a makefile and the xcode3 workspace. + +- Andreas Nagel +First Assimp testing & verification under Windows Vista 64 Bit. + +- Marius Schr�der +Allowed us to use many of his models for screenshots and testing. + +- Christian Schubert +Supplied various XFiles for testing purposes. + +- Tizian Wieland +Searched the web for hundreds of test models for internal use + +- John Connors +Supplied patches for linux and SCons. + +- T. R. +The GUY who performed some of the CSM mocaps. + +- Andy Maloney +Contributed fixes for the documentation and the doxygen markup + +- Zhao Lei +Contributed several bugfixes fixing memory leaks and improving float parsing + +- sueastside +Updated PyAssimp to the latest Assimp data structures and provided a script to keep the Python binding up-to-date. + +- Tobias Rittig +Collada testing with Cinema 4D + +- Brad Grantham +Improvements in OpenGL-Sample. + +- Robert Ramirez +Add group loading feature to Obj-Loader. + +- Chris Maiwald +Many bugreports, improving Assimp's portability, regular testing & feedback. + +- Stepan Hrbek +Bugreport and fix for a obj-materialloader crash. + +- David Nadlinger +D bindings, CMake install support. + +- Dario Accornero +Contributed several patches regarding Mac OS/XCode targets, bug reports. + +- Martin Walser (Samhayne) +Contributed the 'SimpleTexturedOpenGl' sample. + +- Matthias Fauconneau +Contributed a fix for the Q3-BSP loader. + +- Jørgen P. Tjernø +Contributed updated and improved xcode workspaces + +- drparallax +Contributed the /samples/SimpleAssimpViewX sample + +- Carsten Fuchs +Contributed a fix for the Normalize method in aiQuaternion. + +- dbburgess +Contributes a Android-specific build issue: log the hardware architecture for ARM. + +- alfiereinre7 +Contributes a obj-fileparser fix: missing tokens in the obj-token list. + +- Roman Kharitonov +Contributes a fix for the configure script environment. + +- Ed Diana +Contributed AssimpDelphi (/port/AssimpDelphi). + +- rdb +Contributes a bundle of fixes and improvements for the bsp-importer. + +- Mick P +For contributing the De-bone postprocessing step and filing various bug reports. + +- Rosen Diankov +Contributed patches to build assimp debian packages using cmake. + +- Mark Page +Contributed a patch to fix the VertexTriangleAdjacency postprocessing step. + +- IOhannes +Contributed the Debian build fixes ( architecture macro ). + +- gellule +Several LWO and LWS fixes (pivoting). + +- Marcel Metz +GCC/Linux fixes for the SimpleOpenGL sample. + +- Brian Miller +Bugfix for a compiler fix for iOS on arm. + +- Séverin Lemaignan +Rewrite of PyAssimp, distutils and Python3 support + +- albert-wang +Bugfixes for the collada parser + +- Ya ping Jin +Bugfixes for uv-tanget calculation. + +- Jonne Nauha +Ogre Binary format support + +- Filip Wasil, Tieto Poland Sp. z o.o. +Android JNI asset extraction support + +- Richard Steffen +Contributed ExportProperties interface +Contributed X File exporter +Contributed Step (stp) exporter + +- Thomas Iorns (mesilliac) +Initial FBX Export support + +For a more detailed list just check: https://github.com/assimp/assimp/network/members + + +======== +Patreons +======== + +Huge thanks to our Patreons! + +- migenius +- Marcus +- Cort +- elect +- Steffen + + +=================== +Commercial Sponsors +=================== + +- MyDidimo (mydidimo.com): Sponsored development of FBX Export support diff --git a/modules/fbx/fbx_parser/FBXAnimation.cpp b/modules/fbx/fbx_parser/FBXAnimation.cpp new file mode 100644 index 0000000000..bfd3e97314 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXAnimation.cpp @@ -0,0 +1,290 @@ +/*************************************************************************/ +/* FBXAnimation.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXAnimation.cpp + * @brief Assimp::FBX::AnimationCurve, Assimp::FBX::AnimationCurveNode, + * Assimp::FBX::AnimationLayer, Assimp::FBX::AnimationStack + */ + +#include "FBXCommon.h" +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXParser.h" + +namespace FBXDocParser { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +AnimationCurve::AnimationCurve(uint64_t id, const ElementPtr element, const std::string &name, const Document & /*doc*/) : + Object(id, element, name) { + const ScopePtr sc = GetRequiredScope(element); + const ElementPtr KeyTime = GetRequiredElement(sc, "KeyTime"); + const ElementPtr KeyValueFloat = GetRequiredElement(sc, "KeyValueFloat"); + + // note preserved keys and values for legacy FBXConverter.cpp + // we can remove this once the animation system is written + // and clean up this code so we do not have copies everywhere. + ParseVectorDataArray(keys, KeyTime); + ParseVectorDataArray(values, KeyValueFloat); + + if (keys.size() != values.size()) { + DOMError("the number of key times does not match the number of keyframe values", KeyTime); + } + + // put the two lists into the map, underlying container is really just a dictionary + // these will always match, if not an error will throw and the file will not import + // this is useful because we then can report something and fix this later if it becomes an issue + // at this point we do not need a different count of these elements so this makes the + // most sense to do. + for (size_t x = 0; x < keys.size(); x++) { + keyvalues[keys[x]] = values[x]; + } + + const ElementPtr KeyAttrDataFloat = sc->GetElement("KeyAttrDataFloat"); + if (KeyAttrDataFloat) { + ParseVectorDataArray(attributes, KeyAttrDataFloat); + } + + const ElementPtr KeyAttrFlags = sc->GetElement("KeyAttrFlags"); + if (KeyAttrFlags) { + ParseVectorDataArray(flags, KeyAttrFlags); + } +} + +// ------------------------------------------------------------------------------------------------ +AnimationCurve::~AnimationCurve() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +AnimationCurveNode::AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name, + const Document &doc, const char *const *target_prop_whitelist /*= NULL*/, + size_t whitelist_size /*= 0*/) : + Object(id, element, name), target(), doc(doc) { + const ScopePtr sc = GetRequiredScope(element); + + // find target node + const char *whitelist[] = { "Model", "NodeAttribute", "Deformer" }; + const std::vector<const Connection *> &conns = doc.GetConnectionsBySourceSequenced(ID(), whitelist, 3); + + for (const Connection *con : conns) { + // link should go for a property + if (!con->PropertyName().length()) { + continue; + } + + Object *object = con->DestinationObject(); + + if (!object) { + DOMWarning("failed to read destination object for AnimationCurveNode->Model link, ignoring", element); + continue; + } + + target = object; + prop = con->PropertyName(); + break; + } + + props = GetPropertyTable(doc, "AnimationCurveNode.FbxAnimCurveNode", element, sc, false); +} + +// ------------------------------------------------------------------------------------------------ +AnimationCurveNode::~AnimationCurveNode() { + curves.clear(); +} + +// ------------------------------------------------------------------------------------------------ +const AnimationMap &AnimationCurveNode::Curves() const { + /* Lazy loaded animation curves, will only load if required */ + if (curves.empty()) { + // resolve attached animation curves + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurve"); + + for (const Connection *con : conns) { + // So the advantage of having this STL boilerplate is that it's dead simple once you get it. + // The other advantage is casting is guaranteed to be safe and nullptr will be returned in the last step if it fails. + Object *ob = con->SourceObject(); + AnimationCurve *anim_curve = dynamic_cast<AnimationCurve *>(ob); + ERR_CONTINUE_MSG(!anim_curve, "Failed to convert animation curve from object"); + + curves.insert(std::make_pair(con->PropertyName(), anim_curve)); + } + } + + return curves; +} + +// ------------------------------------------------------------------------------------------------ +AnimationLayer::AnimationLayer(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : + Object(id, element, name), doc(doc) { + const ScopePtr sc = GetRequiredScope(element); + + // note: the props table here bears little importance and is usually absent + props = GetPropertyTable(doc, "AnimationLayer.FbxAnimLayer", element, sc, true); +} + +// ------------------------------------------------------------------------------------------------ +AnimationLayer::~AnimationLayer() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +const AnimationCurveNodeList AnimationLayer::Nodes(const char *const *target_prop_whitelist, + size_t whitelist_size /*= 0*/) const { + AnimationCurveNodeList nodes; + + // resolve attached animation nodes + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurveNode"); + nodes.reserve(conns.size()); + + for (const Connection *con : conns) { + // link should not go to a property + if (con->PropertyName().length()) { + continue; + } + + Object *ob = con->SourceObject(); + + if (!ob) { + DOMWarning("failed to read source object for AnimationCurveNode->AnimationLayer link, ignoring", element); + continue; + } + + const AnimationCurveNode *anim = dynamic_cast<AnimationCurveNode *>(ob); + if (!anim) { + DOMWarning("source object for ->AnimationLayer link is not an AnimationCurveNode", element); + continue; + } + + if (target_prop_whitelist) { + const char *s = anim->TargetProperty().c_str(); + bool ok = false; + for (size_t i = 0; i < whitelist_size; ++i) { + if (!strcmp(s, target_prop_whitelist[i])) { + ok = true; + break; + } + } + if (!ok) { + continue; + } + } + nodes.push_back(anim); + } + + return nodes; +} + +// ------------------------------------------------------------------------------------------------ +AnimationStack::AnimationStack(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : + Object(id, element, name) { + const ScopePtr sc = GetRequiredScope(element); + + // note: we don't currently use any of these properties so we shouldn't bother if it is missing + props = GetPropertyTable(doc, "AnimationStack.FbxAnimStack", element, sc, true); + + // resolve attached animation layers + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationLayer"); + layers.reserve(conns.size()); + + for (const Connection *con : conns) { + // link should not go to a property + if (con->PropertyName().length()) { + continue; + } + + Object *ob = con->SourceObject(); + if (!ob) { + DOMWarning("failed to read source object for AnimationLayer->AnimationStack link, ignoring", element); + continue; + } + + const AnimationLayer *anim = dynamic_cast<const AnimationLayer *>(ob); + + if (!anim) { + DOMWarning("source object for ->AnimationStack link is not an AnimationLayer", element); + continue; + } + + layers.push_back(anim); + } +} + +// ------------------------------------------------------------------------------------------------ +AnimationStack::~AnimationStack() { + if (props != nullptr) { + delete props; + props = nullptr; + } +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp b/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp new file mode 100644 index 0000000000..0fc66b13cd --- /dev/null +++ b/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp @@ -0,0 +1,467 @@ +/*************************************************************************/ +/* FBXBinaryTokenizer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +/** @file FBXBinaryTokenizer.cpp + * @brief Implementation of a fake lexer for binary fbx files - + * we emit tokens so the parser needs almost no special handling + * for binary files. + */ + +#include "ByteSwapper.h" +#include "FBXTokenizer.h" +#include "core/string/print_string.h" + +#include <stdint.h> + +namespace FBXDocParser { +//enum Flag +//{ +// e_unknown_0 = 1 << 0, +// e_unknown_1 = 1 << 1, +// e_unknown_2 = 1 << 2, +// e_unknown_3 = 1 << 3, +// e_unknown_4 = 1 << 4, +// e_unknown_5 = 1 << 5, +// e_unknown_6 = 1 << 6, +// e_unknown_7 = 1 << 7, +// e_unknown_8 = 1 << 8, +// e_unknown_9 = 1 << 9, +// e_unknown_10 = 1 << 10, +// e_unknown_11 = 1 << 11, +// e_unknown_12 = 1 << 12, +// e_unknown_13 = 1 << 13, +// e_unknown_14 = 1 << 14, +// e_unknown_15 = 1 << 15, +// e_unknown_16 = 1 << 16, +// e_unknown_17 = 1 << 17, +// e_unknown_18 = 1 << 18, +// e_unknown_19 = 1 << 19, +// e_unknown_20 = 1 << 20, +// e_unknown_21 = 1 << 21, +// e_unknown_22 = 1 << 22, +// e_unknown_23 = 1 << 23, +// e_flag_field_size_64_bit = 1 << 24, // Not sure what is +// e_unknown_25 = 1 << 25, +// e_unknown_26 = 1 << 26, +// e_unknown_27 = 1 << 27, +// e_unknown_28 = 1 << 28, +// e_unknown_29 = 1 << 29, +// e_unknown_30 = 1 << 30, +// e_unknown_31 = 1 << 31 +//}; +// +//bool check_flag(uint32_t flags, Flag to_check) +//{ +// return (flags & to_check) != 0; +//} +// ------------------------------------------------------------------------------------------------ +Token::Token(const char *sbegin, const char *send, TokenType type, size_t offset) : + sbegin(sbegin), + send(send), + type(type), + line(offset), + column(BINARY_MARKER) { +#ifdef DEBUG_ENABLED + contents = std::string(sbegin, static_cast<size_t>(send - sbegin)); +#endif + // calc length + // measure from sBegin to sEnd and validate? +} + +namespace { + +// ------------------------------------------------------------------------------------------------ +// signal tokenization error +void TokenizeError(const std::string &message, size_t offset) { + print_error("[FBX-Tokenize] " + String(message.c_str()) + ", offset " + itos(offset)); +} + +// ------------------------------------------------------------------------------------------------ +size_t Offset(const char *begin, const char *cursor) { + //ai_assert(begin <= cursor); + + return cursor - begin; +} + +// ------------------------------------------------------------------------------------------------ +void TokenizeError(const std::string &message, const char *begin, const char *cursor) { + TokenizeError(message, Offset(begin, cursor)); +} + +// ------------------------------------------------------------------------------------------------ +uint32_t ReadWord(const char *input, const char *&cursor, const char *end) { + const size_t k_to_read = sizeof(uint32_t); + if (Offset(cursor, end) < k_to_read) { + TokenizeError("cannot ReadWord, out of bounds", input, cursor); + } + + uint32_t word; + ::memcpy(&word, cursor, 4); + AI_SWAP4(word); + + cursor += k_to_read; + + return word; +} + +// ------------------------------------------------------------------------------------------------ +uint64_t ReadDoubleWord(const char *input, const char *&cursor, const char *end) { + const size_t k_to_read = sizeof(uint64_t); + if (Offset(cursor, end) < k_to_read) { + TokenizeError("cannot ReadDoubleWord, out of bounds", input, cursor); + } + + uint64_t dword /*= *reinterpret_cast<const uint64_t*>(cursor)*/; + ::memcpy(&dword, cursor, sizeof(uint64_t)); + AI_SWAP8(dword); + + cursor += k_to_read; + + return dword; +} + +// ------------------------------------------------------------------------------------------------ +uint8_t ReadByte(const char *input, const char *&cursor, const char *end) { + if (Offset(cursor, end) < sizeof(uint8_t)) { + TokenizeError("cannot ReadByte, out of bounds", input, cursor); + } + + uint8_t word; /* = *reinterpret_cast< const uint8_t* >( cursor )*/ + ::memcpy(&word, cursor, sizeof(uint8_t)); + ++cursor; + + return word; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int ReadString(const char *&sbegin_out, const char *&send_out, const char *input, + const char *&cursor, const char *end, bool long_length = false, bool allow_null = false) { + const uint32_t len_len = long_length ? 4 : 1; + if (Offset(cursor, end) < len_len) { + TokenizeError("cannot ReadString, out of bounds reading length", input, cursor); + } + + const uint32_t length = long_length ? ReadWord(input, cursor, end) : ReadByte(input, cursor, end); + + if (Offset(cursor, end) < length) { + TokenizeError("cannot ReadString, length is out of bounds", input, cursor); + } + + sbegin_out = cursor; + cursor += length; + + send_out = cursor; + + if (!allow_null) { + for (unsigned int i = 0; i < length; ++i) { + if (sbegin_out[i] == '\0') { + TokenizeError("failed ReadString, unexpected NUL character in string", input, cursor); + } + } + } + + return length; +} + +// ------------------------------------------------------------------------------------------------ +void ReadData(const char *&sbegin_out, const char *&send_out, const char *input, const char *&cursor, const char *end) { + if (Offset(cursor, end) < 1) { + TokenizeError("cannot ReadData, out of bounds reading length", input, cursor); + } + + const char type = *cursor; + sbegin_out = cursor++; + + switch (type) { + // 16 bit int + case 'Y': + cursor += 2; + break; + + // 1 bit bool flag (yes/no) + case 'C': + cursor += 1; + break; + + // 32 bit int + case 'I': + // <- fall through + + // float + case 'F': + cursor += 4; + break; + + // double + case 'D': + cursor += 8; + break; + + // 64 bit int + case 'L': + cursor += 8; + break; + + // note: do not write cursor += ReadWord(...cursor) as this would be UB + + // raw binary data + case 'R': { + const uint32_t length = ReadWord(input, cursor, end); + cursor += length; + break; + } + + case 'b': + // TODO: what is the 'b' type code? Right now we just skip over it / + // take the full range we could get + cursor = end; + break; + + // array of * + case 'f': + case 'd': + case 'l': + case 'i': + case 'c': { + const uint32_t length = ReadWord(input, cursor, end); + const uint32_t encoding = ReadWord(input, cursor, end); + + const uint32_t comp_len = ReadWord(input, cursor, end); + + // compute length based on type and check against the stored value + if (encoding == 0) { + uint32_t stride = 0; + switch (type) { + case 'f': + case 'i': + stride = 4; + break; + + case 'd': + case 'l': + stride = 8; + break; + + case 'c': + stride = 1; + break; + + default: + break; + }; + //ai_assert(stride > 0); + if (length * stride != comp_len) { + TokenizeError("cannot ReadData, calculated data stride differs from what the file claims", input, cursor); + } + } + // zip/deflate algorithm (encoding==1)? take given length. anything else? die + else if (encoding != 1) { + TokenizeError("cannot ReadData, unknown encoding", input, cursor); + } + cursor += comp_len; + break; + } + + // string + case 'S': { + const char *sb, *se; + // 0 characters can legally happen in such strings + ReadString(sb, se, input, cursor, end, true, true); + break; + } + default: + TokenizeError("cannot ReadData, unexpected type code: " + std::string(&type, 1), input, cursor); + } + + if (cursor > end) { + TokenizeError("cannot ReadData, the remaining size is too small for the data type: " + std::string(&type, 1), input, cursor); + } + + // the type code is contained in the returned range + send_out = cursor; +} + +// ------------------------------------------------------------------------------------------------ +bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, const char *end, bool const is64bits) { + // the first word contains the offset at which this block ends + const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); + + // we may get 0 if reading reached the end of the file - + // fbx files have a mysterious extra footer which I don't know + // how to extract any information from, but at least it always + // starts with a 0. + if (!end_offset) { + return false; + } + + if (end_offset > Offset(input, end)) { + TokenizeError("block offset is out of range", input, cursor); + } else if (end_offset < Offset(input, cursor)) { + TokenizeError("block offset is negative out of range", input, cursor); + } + + // the second data word contains the number of properties in the scope + const uint64_t prop_count = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); + + // the third data word contains the length of the property list + const uint64_t prop_length = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); + + // now comes the name of the scope/key + const char *sbeg, *send; + ReadString(sbeg, send, input, cursor, end); + + output_tokens.push_back(new_Token(sbeg, send, TokenType_KEY, Offset(input, cursor))); + + // now come the individual properties + const char *begin_cursor = cursor; + for (unsigned int i = 0; i < prop_count; ++i) { + ReadData(sbeg, send, input, cursor, begin_cursor + prop_length); + + output_tokens.push_back(new_Token(sbeg, send, TokenType_DATA, Offset(input, cursor))); + + if (i != prop_count - 1) { + output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_COMMA, Offset(input, cursor))); + } + } + + if (Offset(begin_cursor, cursor) != prop_length) { + TokenizeError("property length not reached, something is wrong", input, cursor); + } + + // at the end of each nested block, there is a NUL record to indicate + // that the sub-scope exists (i.e. to distinguish between P: and P : {}) + // this NUL record is 13 bytes long on 32 bit version and 25 bytes long on 64 bit. + const size_t sentinel_block_length = is64bits ? (sizeof(uint64_t) * 3 + 1) : (sizeof(uint32_t) * 3 + 1); + + if (Offset(input, cursor) < end_offset) { + if (end_offset - Offset(input, cursor) < sentinel_block_length) { + TokenizeError("insufficient padding bytes at block end", input, cursor); + } + + output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_OPEN_BRACKET, Offset(input, cursor))); + + // XXX this is vulnerable to stack overflowing .. + while (Offset(input, cursor) < end_offset - sentinel_block_length) { + ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits); + } + output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor))); + + for (unsigned int i = 0; i < sentinel_block_length; ++i) { + if (cursor[i] != '\0') { + TokenizeError("failed to read nested block sentinel, expected all bytes to be 0", input, cursor); + } + } + cursor += sentinel_block_length; + } + + if (Offset(input, cursor) != end_offset) { + TokenizeError("scope length not reached, something is wrong", input, cursor); + } + + return true; +} +} // anonymous namespace + +// ------------------------------------------------------------------------------------------------ +// TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent +void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length) { + if (length < 0x1b) { + //TokenizeError("file is too short",0); + } + + //uint32_t offset = 0x15; + /* const char* cursor = input + 0x15; + const uint32_t flags = ReadWord(input, cursor, input + length); + const uint8_t padding_0 = ReadByte(input, cursor, input + length); // unused + const uint8_t padding_1 = ReadByte(input, cursor, input + length); // unused*/ + + if (strncmp(input, "Kaydara FBX Binary", 18)) { + TokenizeError("magic bytes not found", 0); + } + + const char *cursor = input + 18; + /*Result ignored*/ ReadByte(input, cursor, input + length); + /*Result ignored*/ ReadByte(input, cursor, input + length); + /*Result ignored*/ ReadByte(input, cursor, input + length); + /*Result ignored*/ ReadByte(input, cursor, input + length); + /*Result ignored*/ ReadByte(input, cursor, input + length); + const uint32_t version = ReadWord(input, cursor, input + length); + print_verbose("FBX Version: " + itos(version)); + //ASSIMP_LOG_DEBUG_F("FBX version: ", version); + const bool is64bits = version >= 7500; + const char *end = input + length; + while (cursor < end) { + if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) { + break; + } + } +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXCommon.h b/modules/fbx/fbx_parser/FBXCommon.h new file mode 100644 index 0000000000..98a3d78f0d --- /dev/null +++ b/modules/fbx/fbx_parser/FBXCommon.h @@ -0,0 +1,110 @@ +/*************************************************************************/ +/* FBXCommon.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXCommon.h +* Some useful constants and enums for dealing with FBX files. +*/ +#ifndef FBX_COMMON_H +#define FBX_COMMON_H + +#include <string> + +namespace FBXDocParser { +const std::string NULL_RECORD = { // 13 null bytes + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' +}; // who knows why +const std::string SEPARATOR = { '\x00', '\x01' }; // for use inside strings +const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import +const int64_t SECOND = 46186158000; // FBX's kTime unit + +// rotation order. We'll probably use EulerXYZ for everything +enum RotOrder { + RotOrder_EulerXYZ = 0, + RotOrder_EulerXZY, + RotOrder_EulerYZX, + RotOrder_EulerYXZ, + RotOrder_EulerZXY, + RotOrder_EulerZYX, + + RotOrder_SphericXYZ, + + RotOrder_MAX // end-of-enum sentinel +}; + +enum TransformInheritance { + Transform_RrSs = 0, + Transform_RSrs = 1, + Transform_Rrs = 2, + TransformInheritance_MAX // end-of-enum sentinel +}; +} // namespace FBXDocParser + +#endif // FBX_COMMON_H diff --git a/modules/fbx/fbx_parser/FBXDeformer.cpp b/modules/fbx/fbx_parser/FBXDeformer.cpp new file mode 100644 index 0000000000..325641bc32 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXDeformer.cpp @@ -0,0 +1,279 @@ +/*************************************************************************/ +/* FBXDeformer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXNoteAttribute.cpp + * @brief Assimp::FBX::NodeAttribute (and subclasses) implementation + */ + +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXMeshGeometry.h" +#include "FBXParser.h" +#include "core/math/math_funcs.h" +#include "core/math/transform.h" + +#include <iostream> + +namespace FBXDocParser { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Deformer::Deformer(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Object(id, element, name) { + const ScopePtr sc = GetRequiredScope(element); + + const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2)); + props = GetPropertyTable(doc, "Deformer.Fbx" + classname, element, sc, true); +} + +// ------------------------------------------------------------------------------------------------ +Deformer::~Deformer() { +} + +Constraint::Constraint(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Object(id, element, name) { + const ScopePtr sc = GetRequiredScope(element); + const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2)); + // used something.fbx as this is a cache name. + props = GetPropertyTable(doc, "Something.Fbx" + classname, element, sc, true); +} + +Constraint::~Constraint() { +} + +// ------------------------------------------------------------------------------------------------ +Cluster::Cluster(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Deformer(id, element, doc, name), valid_transformAssociateModel(false) { + const ScopePtr sc = GetRequiredScope(element); + // for( auto element : sc.Elements()) + // { + // std::cout << "cluster element: " << element.first << std::endl; + // } + // + // element: Indexes + // element: Transform + // element: TransformAssociateModel + // element: TransformLink + // element: UserData + // element: Version + // element: Weights + + const ElementPtr Indexes = sc->GetElement("Indexes"); + const ElementPtr Weights = sc->GetElement("Weights"); + + const ElementPtr TransformAssociateModel = sc->GetElement("TransformAssociateModel"); + if (TransformAssociateModel != nullptr) { + //Transform t = ReadMatrix(*TransformAssociateModel); + link_mode = SkinLinkMode_Additive; + valid_transformAssociateModel = true; + } else { + link_mode = SkinLinkMode_Normalized; + valid_transformAssociateModel = false; + } + + const ElementPtr Transform = GetRequiredElement(sc, "Transform", element); + const ElementPtr TransformLink = GetRequiredElement(sc, "TransformLink", element); + + // todo: check if we need this + //const Element& TransformAssociateModel = GetRequiredElement(sc, "TransformAssociateModel", &element); + + transform = ReadMatrix(Transform); + transformLink = ReadMatrix(TransformLink); + + // it is actually possible that there be Deformer's with no weights + if (!!Indexes != !!Weights) { + DOMError("either Indexes or Weights are missing from Cluster", element); + } + + if (Indexes) { + ParseVectorDataArray(indices, Indexes); + ParseVectorDataArray(weights, Weights); + } + + if (indices.size() != weights.size()) { + DOMError("sizes of index and weight array don't match up", element); + } + + // read assigned node + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Model"); + for (const Connection *con : conns) { + const Model *mod = ProcessSimpleConnection<Model>(*con, false, "Model -> Cluster", element); + if (mod) { + node = mod; + break; + } + } + + if (!node) { + DOMError("failed to read target Node for Cluster", element); + node = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +Cluster::~Cluster() { +} + +// ------------------------------------------------------------------------------------------------ +Skin::Skin(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Deformer(id, element, doc, name), accuracy(0.0f) { + const ScopePtr sc = GetRequiredScope(element); + + // keep this it is used for debugging and any FBX format changes + // for (auto element : sc.Elements()) { + // std::cout << "skin element: " << element.first << std::endl; + // } + + const ElementPtr Link_DeformAcuracy = sc->GetElement("Link_DeformAcuracy"); + if (Link_DeformAcuracy) { + accuracy = ParseTokenAsFloat(GetRequiredToken(Link_DeformAcuracy, 0)); + } + + const ElementPtr SkinType = sc->GetElement("SkinningType"); + + if (SkinType) { + std::string skin_type = ParseTokenAsString(GetRequiredToken(SkinType, 0)); + + if (skin_type == "Linear") { + skinType = Skin_Linear; + } else if (skin_type == "Rigid") { + skinType = Skin_Rigid; + } else if (skin_type == "DualQuaternion") { + skinType = Skin_DualQuaternion; + } else if (skin_type == "Blend") { + skinType = Skin_Blend; + } else { + print_error("[doc:skin] could not find valid skin type: " + String(skin_type.c_str())); + } + } + + // resolve assigned clusters + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer"); + + // + + clusters.reserve(conns.size()); + for (const Connection *con : conns) { + const Cluster *cluster = ProcessSimpleConnection<Cluster>(*con, false, "Cluster -> Skin", element); + if (cluster) { + clusters.push_back(cluster); + continue; + } + } +} + +// ------------------------------------------------------------------------------------------------ +Skin::~Skin() { +} +// ------------------------------------------------------------------------------------------------ +BlendShape::BlendShape(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Deformer(id, element, doc, name) { + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer"); + blendShapeChannels.reserve(conns.size()); + for (const Connection *con : conns) { + const BlendShapeChannel *bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element); + if (bspc) { + blendShapeChannels.push_back(bspc); + continue; + } + } +} +// ------------------------------------------------------------------------------------------------ +BlendShape::~BlendShape() { +} +// ------------------------------------------------------------------------------------------------ +BlendShapeChannel::BlendShapeChannel(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Deformer(id, element, doc, name) { + const ScopePtr sc = GetRequiredScope(element); + const ElementPtr DeformPercent = sc->GetElement("DeformPercent"); + if (DeformPercent) { + percent = ParseTokenAsFloat(GetRequiredToken(DeformPercent, 0)); + } + const ElementPtr FullWeights = sc->GetElement("FullWeights"); + if (FullWeights) { + ParseVectorDataArray(fullWeights, FullWeights); + } + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Geometry"); + shapeGeometries.reserve(conns.size()); + for (const Connection *con : conns) { + const ShapeGeometry *const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element); + if (sg) { + shapeGeometries.push_back(sg); + continue; + } + } +} +// ------------------------------------------------------------------------------------------------ +BlendShapeChannel::~BlendShapeChannel() { +} +// ------------------------------------------------------------------------------------------------ +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXDocument.cpp b/modules/fbx/fbx_parser/FBXDocument.cpp new file mode 100644 index 0000000000..74798a655a --- /dev/null +++ b/modules/fbx/fbx_parser/FBXDocument.cpp @@ -0,0 +1,713 @@ +/*************************************************************************/ +/* FBXDocument.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the* + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXDocument.cpp + * @brief Implementation of the FBX DOM classes + */ + +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXImportSettings.h" +#include "FBXMeshGeometry.h" +#include "FBXParser.h" +#include "FBXProperties.h" +#include "FBXUtil.h" + +#include <algorithm> +#include <functional> +#include <iostream> +#include <map> +#include <memory> + +namespace FBXDocParser { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +LazyObject::LazyObject(uint64_t id, const ElementPtr element, const Document &doc) : + doc(doc), element(element), id(id), flags() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +LazyObject::~LazyObject() { + object.reset(); +} + +ObjectPtr LazyObject::LoadObject() { + if (IsBeingConstructed() || FailedToConstruct()) { + return nullptr; + } + + if (object) { + return object.get(); + } + + TokenPtr key = element->KeyToken(); + ERR_FAIL_COND_V(!key, nullptr); + const TokenList &tokens = element->Tokens(); + + if (tokens.size() < 3) { + //DOMError("expected at least 3 tokens: id, name and class tag",&element); + return nullptr; + } + + const char *err = nullptr; + std::string name = ParseTokenAsString(tokens[1], err); + if (err) { + DOMError(err, element); + } + + // small fix for binary reading: binary fbx files don't use + // prefixes such as Model:: in front of their names. The + // loading code expects this at many places, though! + // so convert the binary representation (a 0x0001) to the + // double colon notation. + if (tokens[1]->IsBinary()) { + for (size_t i = 0; i < name.length(); ++i) { + if (name[i] == 0x0 && name[i + 1] == 0x1) { + name = name.substr(i + 2) + "::" + name.substr(0, i); + } + } + } + + const std::string classtag = ParseTokenAsString(tokens[2], err); + if (err) { + DOMError(err, element); + } + + // prevent recursive calls + flags |= BEING_CONSTRUCTED; + + // this needs to be relatively fast since it happens a lot, + // so avoid constructing strings all the time. + const char *obtype = key->begin(); + const size_t length = static_cast<size_t>(key->end() - key->begin()); + + if (!strncmp(obtype, "Pose", length)) { + object.reset(new FbxPose(id, element, doc, name)); + } else if (!strncmp(obtype, "Geometry", length)) { + if (!strcmp(classtag.c_str(), "Mesh")) { + object.reset(new MeshGeometry(id, element, name, doc)); + } + if (!strcmp(classtag.c_str(), "Shape")) { + object.reset(new ShapeGeometry(id, element, name, doc)); + } + if (!strcmp(classtag.c_str(), "Line")) { + object.reset(new LineGeometry(id, element, name, doc)); + } + } else if (!strncmp(obtype, "NodeAttribute", length)) { + if (!strcmp(classtag.c_str(), "Camera")) { + object.reset(new Camera(id, element, doc, name)); + } else if (!strcmp(classtag.c_str(), "CameraSwitcher")) { + object.reset(new CameraSwitcher(id, element, doc, name)); + } else if (!strcmp(classtag.c_str(), "Light")) { + object.reset(new Light(id, element, doc, name)); + } else if (!strcmp(classtag.c_str(), "Null")) { + object.reset(new Null(id, element, doc, name)); + } else if (!strcmp(classtag.c_str(), "LimbNode")) { + // This is an older format for bones + // this is what blender uses I believe + object.reset(new LimbNode(id, element, doc, name)); + } + } else if (!strncmp(obtype, "Constraint", length)) { + object.reset(new Constraint(id, element, doc, name)); + } else if (!strncmp(obtype, "Deformer", length)) { + if (!strcmp(classtag.c_str(), "Cluster")) { + object.reset(new Cluster(id, element, doc, name)); + } else if (!strcmp(classtag.c_str(), "Skin")) { + object.reset(new Skin(id, element, doc, name)); + } else if (!strcmp(classtag.c_str(), "BlendShape")) { + object.reset(new BlendShape(id, element, doc, name)); + } else if (!strcmp(classtag.c_str(), "BlendShapeChannel")) { + object.reset(new BlendShapeChannel(id, element, doc, name)); + } + } else if (!strncmp(obtype, "Model", length)) { + // Model is normal node + + // LimbNode model is a 'bone' node. + if (!strcmp(classtag.c_str(), "LimbNode")) { + object.reset(new ModelLimbNode(id, element, doc, name)); + + } else if (strcmp(classtag.c_str(), "IKEffector") && strcmp(classtag.c_str(), "FKEffector")) { + // FK and IK effectors are not supporte + object.reset(new Model(id, element, doc, name)); + } + } else if (!strncmp(obtype, "Material", length)) { + object.reset(new Material(id, element, doc, name)); + } else if (!strncmp(obtype, "Texture", length)) { + object.reset(new Texture(id, element, doc, name)); + } else if (!strncmp(obtype, "LayeredTexture", length)) { + object.reset(new LayeredTexture(id, element, doc, name)); + } else if (!strncmp(obtype, "Video", length)) { + object.reset(new Video(id, element, doc, name)); + } else if (!strncmp(obtype, "AnimationStack", length)) { + object.reset(new AnimationStack(id, element, name, doc)); + } else if (!strncmp(obtype, "AnimationLayer", length)) { + object.reset(new AnimationLayer(id, element, name, doc)); + } else if (!strncmp(obtype, "AnimationCurve", length)) { + object.reset(new AnimationCurve(id, element, name, doc)); + } else if (!strncmp(obtype, "AnimationCurveNode", length)) { + object.reset(new AnimationCurveNode(id, element, name, doc)); + } else { + ERR_FAIL_V_MSG(nullptr, "FBX contains unsupported object: " + String(obtype)); + } + + flags &= ~BEING_CONSTRUCTED; + + return object.get(); +} + +// ------------------------------------------------------------------------------------------------ +Object::Object(uint64_t id, const ElementPtr element, const std::string &name) : + element(element), name(name), id(id) { +} + +// ------------------------------------------------------------------------------------------------ +Object::~Object() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +FileGlobalSettings::FileGlobalSettings(const Document &doc, const PropertyTable *props) : + props(props), doc(doc) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +FileGlobalSettings::~FileGlobalSettings() { + if (props != nullptr) { + delete props; + props = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +Document::Document(const Parser &parser, const ImportSettings &settings) : + settings(settings), parser(parser), SafeToImport(false) { + // Cannot use array default initialization syntax because vc8 fails on it + for (unsigned int &timeStamp : creationTimeStamp) { + timeStamp = 0; + } + + // we must check if we can read the header version safely, if its outdated then drop it. + if (ReadHeader()) { + SafeToImport = true; + ReadPropertyTemplates(); + + ReadGlobalSettings(); + + // This order is important, connections need parsed objects to check + // whether connections are ok or not. Objects may not be evaluated yet, + // though, since this may require valid connections. + ReadObjects(); + ReadConnections(); + } +} + +// ------------------------------------------------------------------------------------------------ +Document::~Document() { + for (PropertyTemplateMap::value_type v : templates) { + delete v.second; + } + + for (ObjectMap::value_type &v : objects) { + delete v.second; + } + + for (ConnectionMap::value_type &v : src_connections) { + delete v.second; + } + + if (metadata_properties != nullptr) { + delete metadata_properties; + } + // clear globals import pointer + globals.reset(); +} + +// ------------------------------------------------------------------------------------------------ +static const unsigned int LowerSupportedVersion = 7300; +static const unsigned int UpperSupportedVersion = 7700; + +bool Document::ReadHeader() { + // Read ID objects from "Objects" section + ScopePtr sc = parser.GetRootScope(); + ElementPtr ehead = sc->GetElement("FBXHeaderExtension"); + if (!ehead || !ehead->Compound()) { + DOMError("no FBXHeaderExtension dictionary found"); + } + + const ScopePtr shead = ehead->Compound(); + fbxVersion = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(shead, "FBXVersion", ehead), 0)); + + // While we may have some success with newer files, we don't support + // the older 6.n fbx format + if (fbxVersion < LowerSupportedVersion) { + DOMWarning("unsupported, old format version, FBX 2015-2020, you must re-export in a more modern version of your original modelling application"); + return false; + } + if (fbxVersion > UpperSupportedVersion) { + DOMWarning("unsupported, newer format version, supported are only FBX 2015, up to FBX 2020" + " trying to read it nevertheless"); + } + + const ElementPtr ecreator = shead->GetElement("Creator"); + if (ecreator) { + creator = ParseTokenAsString(GetRequiredToken(ecreator, 0)); + } + + // + // Scene Info + // + + const ElementPtr scene_info = shead->GetElement("SceneInfo"); + + if (scene_info) { + PropertyTable *fileExportProps = const_cast<PropertyTable *>(GetPropertyTable(*this, "", scene_info, scene_info->Compound(), true)); + + if (fileExportProps) { + metadata_properties = fileExportProps; + } + } + + const ElementPtr etimestamp = shead->GetElement("CreationTimeStamp"); + if (etimestamp && etimestamp->Compound()) { + const ScopePtr stimestamp = etimestamp->Compound(); + creationTimeStamp[0] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Year"), 0)); + creationTimeStamp[1] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Month"), 0)); + creationTimeStamp[2] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Day"), 0)); + creationTimeStamp[3] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Hour"), 0)); + creationTimeStamp[4] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Minute"), 0)); + creationTimeStamp[5] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Second"), 0)); + creationTimeStamp[6] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Millisecond"), 0)); + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ +void Document::ReadGlobalSettings() { + ERR_FAIL_COND_MSG(globals != nullptr, "Global settings is already setup this is a serious error and should be reported"); + + const ScopePtr sc = parser.GetRootScope(); + const ElementPtr ehead = sc->GetElement("GlobalSettings"); + if (nullptr == ehead || !ehead->Compound()) { + DOMWarning("no GlobalSettings dictionary found"); + globals = std::make_shared<FileGlobalSettings>(*this, new PropertyTable()); + return; + } + + const PropertyTable *props = GetPropertyTable(*this, "", ehead, ehead->Compound(), true); + + //double v = PropertyGet<float>( *props, std::string("UnitScaleFactor"), 1.0 ); + + if (!props) { + DOMError("GlobalSettings dictionary contains no property table"); + } + + globals = std::make_shared<FileGlobalSettings>(*this, props); +} + +// ------------------------------------------------------------------------------------------------ +void Document::ReadObjects() { + // read ID objects from "Objects" section + const ScopePtr sc = parser.GetRootScope(); + const ElementPtr eobjects = sc->GetElement("Objects"); + if (!eobjects || !eobjects->Compound()) { + DOMError("no Objects dictionary found"); + } + + // add a dummy entry to represent the Model::RootNode object (id 0), + // which is only indirectly defined in the input file + objects[0] = new LazyObject(0L, eobjects, *this); + + const ScopePtr sobjects = eobjects->Compound(); + for (const ElementMap::value_type &iter : sobjects->Elements()) { + // extract ID + const TokenList &tok = iter.second->Tokens(); + + if (tok.empty()) { + DOMError("expected ID after object key", iter.second); + } + + const char *err; + const uint64_t id = ParseTokenAsID(tok[0], err); + if (err) { + DOMError(err, iter.second); + } + + // id=0 is normally implicit + if (id == 0L) { + DOMError("encountered object with implicitly defined id 0", iter.second); + } + + if (objects.find(id) != objects.end()) { + DOMWarning("encountered duplicate object id, ignoring first occurrence", iter.second); + } + + objects[id] = new LazyObject(id, iter.second, *this); + + // grab all animation stacks upfront since there is no listing of them + if (!strcmp(iter.first.c_str(), "AnimationStack")) { + animationStacks.push_back(id); + } else if (!strcmp(iter.first.c_str(), "Constraint")) { + constraints.push_back(id); + } else if (!strcmp(iter.first.c_str(), "Pose")) { + bind_poses.push_back(id); + } else if (!strcmp(iter.first.c_str(), "Material")) { + materials.push_back(id); + } else if (!strcmp(iter.first.c_str(), "Deformer")) { + TokenPtr key = iter.second->KeyToken(); + ERR_CONTINUE_MSG(!key, "[parser bug] invalid token key for deformer"); + const TokenList &tokens = iter.second->Tokens(); + const std::string class_tag = ParseTokenAsString(tokens[2], err); + + if (err) { + DOMError(err, iter.second); + } + + if (class_tag == "Skin") { + //print_verbose("registered skin:" + itos(id)); + skins.push_back(id); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void Document::ReadPropertyTemplates() { + const ScopePtr sc = parser.GetRootScope(); + // read property templates from "Definitions" section + const ElementPtr edefs = sc->GetElement("Definitions"); + if (!edefs || !edefs->Compound()) { + DOMWarning("no Definitions dictionary found"); + return; + } + + const ScopePtr sdefs = edefs->Compound(); + const ElementCollection otypes = sdefs->GetCollection("ObjectType"); + for (ElementMap::const_iterator it = otypes.first; it != otypes.second; ++it) { + const ElementPtr el = (*it).second; + const ScopePtr sc_2 = el->Compound(); + if (!sc_2) { + DOMWarning("expected nested scope in ObjectType, ignoring", el); + continue; + } + + const TokenList &tok = el->Tokens(); + if (tok.empty()) { + DOMWarning("expected name for ObjectType element, ignoring", el); + continue; + } + + const std::string &oname = ParseTokenAsString(tok[0]); + + const ElementCollection templs = sc_2->GetCollection("PropertyTemplate"); + for (ElementMap::const_iterator iter = templs.first; iter != templs.second; ++iter) { + const ElementPtr el_2 = (*iter).second; + const ScopePtr sc_3 = el_2->Compound(); + if (!sc_3) { + DOMWarning("expected nested scope in PropertyTemplate, ignoring", el); + continue; + } + + const TokenList &tok_2 = el_2->Tokens(); + if (tok_2.empty()) { + DOMWarning("expected name for PropertyTemplate element, ignoring", el); + continue; + } + + const std::string &pname = ParseTokenAsString(tok_2[0]); + + const ElementPtr Properties70 = sc_3->GetElement("Properties70"); + if (Properties70) { + // PropertyTable(const ElementPtr element, const PropertyTable* templateProps); + const PropertyTable *props = new PropertyTable(Properties70, nullptr); + + templates[oname + "." + pname] = props; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void Document::ReadConnections() { + const ScopePtr sc = parser.GetRootScope(); + + // read property templates from "Definitions" section + const ElementPtr econns = sc->GetElement("Connections"); + if (!econns || !econns->Compound()) { + DOMError("no Connections dictionary found"); + } + + uint64_t insertionOrder = 0l; + const ScopePtr sconns = econns->Compound(); + const ElementCollection conns = sconns->GetCollection("C"); + for (ElementMap::const_iterator it = conns.first; it != conns.second; ++it) { + const ElementPtr el = (*it).second; + const std::string &type = ParseTokenAsString(GetRequiredToken(el, 0)); + + // PP = property-property connection, ignored for now + // (tokens: "PP", ID1, "Property1", ID2, "Property2") + if (type == "PP") { + continue; + } + + const uint64_t src = ParseTokenAsID(GetRequiredToken(el, 1)); + const uint64_t dest = ParseTokenAsID(GetRequiredToken(el, 2)); + + // OO = object-object connection + // OP = object-property connection, in which case the destination property follows the object ID + const std::string &prop = (type == "OP" ? ParseTokenAsString(GetRequiredToken(el, 3)) : ""); + + if (objects.find(src) == objects.end()) { + DOMWarning("source object for connection does not exist", el); + continue; + } + + // dest may be 0 (root node) but we added a dummy object before + if (objects.find(dest) == objects.end()) { + DOMWarning("destination object for connection does not exist", el); + continue; + } + + // add new connection + const Connection *const c = new Connection(insertionOrder++, src, dest, prop, *this); + src_connections.insert(ConnectionMap::value_type(src, c)); + dest_connections.insert(ConnectionMap::value_type(dest, c)); + } +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<const AnimationStack *> &Document::AnimationStacks() const { + if (!animationStacksResolved.empty() || animationStacks.empty()) { + return animationStacksResolved; + } + + animationStacksResolved.reserve(animationStacks.size()); + for (uint64_t id : animationStacks) { + LazyObject *lazy = GetObject(id); + + // Two things happen here: + // We cast internally an Object PTR to an Animation Stack PTR + // We return invalid weak_ptrs for objects which are invalid + + const AnimationStack *stack = lazy->Get<AnimationStack>(); + ERR_CONTINUE_MSG(!stack, "invalid ptr to AnimationStack - conversion failure"); + + // We push back the weak reference :) to keep things simple, as ownership is on the parser side so it wont be cleaned up. + animationStacksResolved.push_back(stack); + } + + return animationStacksResolved; +} + +// ------------------------------------------------------------------------------------------------ +LazyObject *Document::GetObject(uint64_t id) const { + ObjectMap::const_iterator it = objects.find(id); + return it == objects.end() ? nullptr : (*it).second; +} + +#define MAX_CLASSNAMES 6 + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection *> Document::GetConnectionsSequenced(uint64_t id, const ConnectionMap &conns) const { + std::vector<const Connection *> temp; + + const std::pair<ConnectionMap::const_iterator, ConnectionMap::const_iterator> range = + conns.equal_range(id); + + temp.reserve(std::distance(range.first, range.second)); + for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) { + temp.push_back((*it).second); + } + + std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare)); + + return temp; // NRVO should handle this +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection *> Document::GetConnectionsSequenced(uint64_t id, bool is_src, + const ConnectionMap &conns, + const char *const *classnames, + size_t count) const + +{ + size_t lengths[MAX_CLASSNAMES]; + + const size_t c = count; + for (size_t i = 0; i < c; ++i) { + lengths[i] = strlen(classnames[i]); + } + + std::vector<const Connection *> temp; + const std::pair<ConnectionMap::const_iterator, ConnectionMap::const_iterator> range = + conns.equal_range(id); + + temp.reserve(std::distance(range.first, range.second)); + for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) { + TokenPtr key = (is_src ? (*it).second->LazyDestinationObject() : (*it).second->LazySourceObject())->GetElement()->KeyToken(); + + const char *obtype = key->begin(); + + for (size_t i = 0; i < c; ++i) { + //ai_assert(classnames[i]); + if (static_cast<size_t>(std::distance(key->begin(), key->end())) == lengths[i] && !strncmp(classnames[i], obtype, lengths[i])) { + obtype = nullptr; + break; + } + } + + if (obtype) { + continue; + } + + temp.push_back((*it).second); + } + + std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare)); + return temp; // NRVO should handle this +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t source) const { + return GetConnectionsSequenced(source, ConnectionsBySource()); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t src, const char *classname) const { + const char *arr[] = { classname }; + return GetConnectionsBySourceSequenced(src, arr, 1); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t source, + const char *const *classnames, size_t count) const { + return GetConnectionsSequenced(source, true, ConnectionsBySource(), classnames, count); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest, + const char *classname) const { + const char *arr[] = { classname }; + return GetConnectionsByDestinationSequenced(dest, arr, 1); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest) const { + return GetConnectionsSequenced(dest, ConnectionsByDestination()); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest, + const char *const *classnames, size_t count) const { + return GetConnectionsSequenced(dest, false, ConnectionsByDestination(), classnames, count); +} + +// ------------------------------------------------------------------------------------------------ +Connection::Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string &prop, + const Document &doc) : + insertionOrder(insertionOrder), prop(prop), src(src), dest(dest), doc(doc) { +} + +// ------------------------------------------------------------------------------------------------ +Connection::~Connection() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +LazyObject *Connection::LazySourceObject() const { + LazyObject *const lazy = doc.GetObject(src); + return lazy; +} + +// ------------------------------------------------------------------------------------------------ +LazyObject *Connection::LazyDestinationObject() const { + LazyObject *const lazy = doc.GetObject(dest); + return lazy; +} + +// ------------------------------------------------------------------------------------------------ +Object *Connection::SourceObject() const { + LazyObject *lazy = doc.GetObject(src); + //ai_assert(lazy); + return lazy->LoadObject(); +} + +// ------------------------------------------------------------------------------------------------ +Object *Connection::DestinationObject() const { + LazyObject *lazy = doc.GetObject(dest); + //ai_assert(lazy); + return lazy->LoadObject(); +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXDocument.h b/modules/fbx/fbx_parser/FBXDocument.h new file mode 100644 index 0000000000..7c08f3de65 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXDocument.h @@ -0,0 +1,1319 @@ +/*************************************************************************/ +/* FBXDocument.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/** @file FBXDocument.h + * @brief FBX DOM + */ +#ifndef FBX_DOCUMENT_H +#define FBX_DOCUMENT_H + +#include "FBXCommon.h" +#include "FBXParser.h" +#include "FBXProperties.h" +#include "core/math/transform.h" +#include "core/math/vector2.h" +#include "core/math/vector3.h" +#include "core/string/print_string.h" +#include <stdint.h> +#include <numeric> + +#define _AI_CONCAT(a, b) a##b +#define AI_CONCAT(a, b) _AI_CONCAT(a, b) + +namespace FBXDocParser { + +class Parser; +class Object; +struct ImportSettings; +class Connection; + +class PropertyTable; +class Document; +class Material; +class ShapeGeometry; +class LineGeometry; +class Geometry; + +class Video; + +class AnimationCurve; +class AnimationCurveNode; +class AnimationLayer; +class AnimationStack; + +class BlendShapeChannel; +class BlendShape; +class Skin; +class Cluster; + +typedef Object *ObjectPtr; +#define new_Object new Object + +/** Represents a delay-parsed FBX objects. Many objects in the scene + * are not needed by assimp, so it makes no sense to parse them + * upfront. */ +class LazyObject { +public: + LazyObject(uint64_t id, const ElementPtr element, const Document &doc); + ~LazyObject(); + + ObjectPtr LoadObject(); + + /* Casting weak pointers to their templated type safely and preserving ref counting and safety + * with lock() keyword to prevent leaking memory + */ + template <typename T> + const T *Get() { + ObjectPtr ob = LoadObject(); + return dynamic_cast<const T *>(ob); + } + + uint64_t ID() const { + return id; + } + + bool IsBeingConstructed() const { + return (flags & BEING_CONSTRUCTED) != 0; + } + + bool FailedToConstruct() const { + return (flags & FAILED_TO_CONSTRUCT) != 0; + } + + ElementPtr GetElement() const { + return element; + } + + const Document &GetDocument() const { + return doc; + } + +private: + const Document &doc; + ElementPtr element = nullptr; + std::shared_ptr<Object> object = nullptr; + const uint64_t id = 0; + + enum Flags { + BEING_CONSTRUCTED = 0x1, + FAILED_TO_CONSTRUCT = 0x2 + }; + + unsigned int flags = 0; +}; + +/** Base class for in-memory (DOM) representations of FBX objects */ +class Object { +public: + Object(uint64_t id, const ElementPtr element, const std::string &name); + + virtual ~Object(); + + ElementPtr SourceElement() const { + return element; + } + + const std::string &Name() const { + return name; + } + + uint64_t ID() const { + return id; + } + +protected: + const ElementPtr element; + const std::string name; + const uint64_t id = 0; +}; + +/** DOM class for generic FBX NoteAttribute blocks. NoteAttribute's just hold a property table, + * fixed members are added by deriving classes. */ +class NodeAttribute : public Object { +public: + NodeAttribute(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~NodeAttribute(); + + const PropertyTable *Props() const { + return props; + } + +private: + const PropertyTable *props; +}; + +/** DOM base class for FBX camera settings attached to a node */ +class CameraSwitcher : public NodeAttribute { +public: + CameraSwitcher(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~CameraSwitcher(); + + int CameraID() const { + return cameraId; + } + + const std::string &CameraName() const { + return cameraName; + } + + const std::string &CameraIndexName() const { + return cameraIndexName; + } + +private: + int cameraId; + std::string cameraName; + std::string cameraIndexName; +}; + +#define fbx_stringize(a) #a + +#define fbx_simple_property(name, type, default_value) \ + type name() const { \ + return PropertyGet<type>(Props(), fbx_stringize(name), (default_value)); \ + } + +// XXX improve logging +#define fbx_simple_enum_property(name, type, default_value) \ + type name() const { \ + const int ival = PropertyGet<int>(Props(), fbx_stringize(name), static_cast<int>(default_value)); \ + if (ival < 0 || ival >= AI_CONCAT(type, _MAX)) { \ + return static_cast<type>(default_value); \ + } \ + return static_cast<type>(ival); \ + } + +class FbxPoseNode; +class FbxPose : public Object { +public: + FbxPose(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + const std::vector<FbxPoseNode *> &GetBindPoses() const { + return pose_nodes; + } + + virtual ~FbxPose(); + +private: + std::vector<FbxPoseNode *> pose_nodes; +}; + +class FbxPoseNode { +public: + FbxPoseNode(const ElementPtr element, const Document &doc, const std::string &name) { + const ScopePtr sc = GetRequiredScope(element); + + // get pose node transform + const ElementPtr Transform = GetRequiredElement(sc, "Matrix", element); + transform = ReadMatrix(Transform); + + // get node id this pose node is for + const ElementPtr NodeId = sc->GetElement("Node3D"); + if (NodeId) { + target_id = ParseTokenAsInt64(GetRequiredToken(NodeId, 0)); + } + + print_verbose("added posenode " + itos(target_id) + " transform: " + transform); + } + virtual ~FbxPoseNode() { + } + + uint64_t GetNodeID() const { + return target_id; + } + + Transform GetBindPose() const { + return transform; + } + +private: + uint64_t target_id; + Transform transform; +}; + +/** DOM base class for FBX cameras attached to a node */ +class Camera : public NodeAttribute { +public: + Camera(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~Camera(); + + fbx_simple_property(Position, Vector3, Vector3(0, 0, 0)); + fbx_simple_property(UpVector, Vector3, Vector3(0, 1, 0)); + fbx_simple_property(InterestPosition, Vector3, Vector3(0, 0, 0)); + + fbx_simple_property(AspectWidth, float, 1.0f); + fbx_simple_property(AspectHeight, float, 1.0f); + fbx_simple_property(FilmWidth, float, 1.0f); + fbx_simple_property(FilmHeight, float, 1.0f); + + fbx_simple_property(NearPlane, float, 0.1f); + fbx_simple_property(FarPlane, float, 100.0f); + + fbx_simple_property(FilmAspectRatio, float, 1.0f); + fbx_simple_property(ApertureMode, int, 0); + + fbx_simple_property(FieldOfView, float, 1.0f); + fbx_simple_property(FocalLength, float, 1.0f); +}; + +/** DOM base class for FBX null markers attached to a node */ +class Null : public NodeAttribute { +public: + Null(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + virtual ~Null(); +}; + +/** DOM base class for FBX limb node markers attached to a node */ +class LimbNode : public NodeAttribute { +public: + LimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + virtual ~LimbNode(); +}; + +/** DOM base class for FBX lights attached to a node */ +class Light : public NodeAttribute { +public: + Light(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + virtual ~Light(); + + enum Type { + Type_Point, + Type_Directional, + Type_Spot, + Type_Area, + Type_Volume, + + Type_MAX // end-of-enum sentinel + }; + + enum Decay { + Decay_None, + Decay_Linear, + Decay_Quadratic, + Decay_Cubic, + + Decay_MAX // end-of-enum sentinel + }; + + fbx_simple_property(Color, Vector3, Vector3(1, 1, 1)); + fbx_simple_enum_property(LightType, Type, 0); + fbx_simple_property(CastLightOnObject, bool, false); + fbx_simple_property(DrawVolumetricLight, bool, true); + fbx_simple_property(DrawGroundProjection, bool, true); + fbx_simple_property(DrawFrontFacingVolumetricLight, bool, false); + fbx_simple_property(Intensity, float, 100.0f); + fbx_simple_property(InnerAngle, float, 0.0f); + fbx_simple_property(OuterAngle, float, 45.0f); + fbx_simple_property(Fog, int, 50); + fbx_simple_enum_property(DecayType, Decay, 2); + fbx_simple_property(DecayStart, float, 1.0f); + fbx_simple_property(FileName, std::string, ""); + + fbx_simple_property(EnableNearAttenuation, bool, false); + fbx_simple_property(NearAttenuationStart, float, 0.0f); + fbx_simple_property(NearAttenuationEnd, float, 0.0f); + fbx_simple_property(EnableFarAttenuation, bool, false); + fbx_simple_property(FarAttenuationStart, float, 0.0f); + fbx_simple_property(FarAttenuationEnd, float, 0.0f); + + fbx_simple_property(CastShadows, bool, true); + fbx_simple_property(ShadowColor, Vector3, Vector3(0, 0, 0)); + + fbx_simple_property(AreaLightShape, int, 0); + + fbx_simple_property(LeftBarnDoor, float, 20.0f); + fbx_simple_property(RightBarnDoor, float, 20.0f); + fbx_simple_property(TopBarnDoor, float, 20.0f); + fbx_simple_property(BottomBarnDoor, float, 20.0f); + fbx_simple_property(EnableBarnDoor, bool, true); +}; + +class Model; + +typedef Model *ModelPtr; +#define new_Model new Model + +/** DOM base class for FBX models (even though its semantics are more "node" than "model" */ +class Model : public Object { +public: + enum RotOrder { + RotOrder_EulerXYZ = 0, + RotOrder_EulerXZY, + RotOrder_EulerYZX, + RotOrder_EulerYXZ, + RotOrder_EulerZXY, + RotOrder_EulerZYX, + + RotOrder_SphericXYZ, + + RotOrder_MAX // end-of-enum sentinel + }; + + Model(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~Model(); + + fbx_simple_property(QuaternionInterpolate, int, 0); + + fbx_simple_property(RotationOffset, Vector3, Vector3()); + fbx_simple_property(RotationPivot, Vector3, Vector3()); + fbx_simple_property(ScalingOffset, Vector3, Vector3()); + fbx_simple_property(ScalingPivot, Vector3, Vector3()); + fbx_simple_property(TranslationActive, bool, false); + fbx_simple_property(TranslationMin, Vector3, Vector3()); + fbx_simple_property(TranslationMax, Vector3, Vector3()); + + fbx_simple_property(TranslationMinX, bool, false); + fbx_simple_property(TranslationMaxX, bool, false); + fbx_simple_property(TranslationMinY, bool, false); + fbx_simple_property(TranslationMaxY, bool, false); + fbx_simple_property(TranslationMinZ, bool, false); + fbx_simple_property(TranslationMaxZ, bool, false); + + fbx_simple_enum_property(RotationOrder, RotOrder, 0); + fbx_simple_property(RotationSpaceForLimitOnly, bool, false); + fbx_simple_property(RotationStiffnessX, float, 0.0f); + fbx_simple_property(RotationStiffnessY, float, 0.0f); + fbx_simple_property(RotationStiffnessZ, float, 0.0f); + fbx_simple_property(AxisLen, float, 0.0f); + + fbx_simple_property(PreRotation, Vector3, Vector3()); + fbx_simple_property(PostRotation, Vector3, Vector3()); + fbx_simple_property(RotationActive, bool, false); + + fbx_simple_property(RotationMin, Vector3, Vector3()); + fbx_simple_property(RotationMax, Vector3, Vector3()); + + fbx_simple_property(RotationMinX, bool, false); + fbx_simple_property(RotationMaxX, bool, false); + fbx_simple_property(RotationMinY, bool, false); + fbx_simple_property(RotationMaxY, bool, false); + fbx_simple_property(RotationMinZ, bool, false); + fbx_simple_property(RotationMaxZ, bool, false); + fbx_simple_enum_property(InheritType, TransformInheritance, 0); + + fbx_simple_property(ScalingActive, bool, false); + fbx_simple_property(ScalingMin, Vector3, Vector3()); + fbx_simple_property(ScalingMax, Vector3, Vector3(1, 1, 1)); + fbx_simple_property(ScalingMinX, bool, false); + fbx_simple_property(ScalingMaxX, bool, false); + fbx_simple_property(ScalingMinY, bool, false); + fbx_simple_property(ScalingMaxY, bool, false); + fbx_simple_property(ScalingMinZ, bool, false); + fbx_simple_property(ScalingMaxZ, bool, false); + + fbx_simple_property(GeometricTranslation, Vector3, Vector3()); + fbx_simple_property(GeometricRotation, Vector3, Vector3()); + fbx_simple_property(GeometricScaling, Vector3, Vector3(1, 1, 1)); + + fbx_simple_property(MinDampRangeX, float, 0.0f); + fbx_simple_property(MinDampRangeY, float, 0.0f); + fbx_simple_property(MinDampRangeZ, float, 0.0f); + fbx_simple_property(MaxDampRangeX, float, 0.0f); + fbx_simple_property(MaxDampRangeY, float, 0.0f); + fbx_simple_property(MaxDampRangeZ, float, 0.0f); + + fbx_simple_property(MinDampStrengthX, float, 0.0f); + fbx_simple_property(MinDampStrengthY, float, 0.0f); + fbx_simple_property(MinDampStrengthZ, float, 0.0f); + fbx_simple_property(MaxDampStrengthX, float, 0.0f); + fbx_simple_property(MaxDampStrengthY, float, 0.0f); + fbx_simple_property(MaxDampStrengthZ, float, 0.0f); + + fbx_simple_property(PreferredAngleX, float, 0.0f); + fbx_simple_property(PreferredAngleY, float, 0.0f); + fbx_simple_property(PreferredAngleZ, float, 0.0f); + + fbx_simple_property(Show, bool, true); + fbx_simple_property(LODBox, bool, false); + fbx_simple_property(Freeze, bool, false); + + const std::string &Shading() const { + return shading; + } + + const std::string &Culling() const { + return culling; + } + + const PropertyTable *Props() const { + return props; + } + + /** Get material links */ + const std::vector<const Material *> &GetMaterials() const { + return materials; + } + + /** Get geometry links */ + const std::vector<const Geometry *> &GetGeometry() const { + return geometry; + } + + /** Get node attachments */ + const std::vector<const NodeAttribute *> &GetAttributes() const { + return attributes; + } + + /** convenience method to check if the node has a Null node marker */ + bool IsNull() const; + +private: + void ResolveLinks(const ElementPtr element, const Document &doc); + +private: + std::vector<const Material *> materials; + std::vector<const Geometry *> geometry; + std::vector<const NodeAttribute *> attributes; + + std::string shading; + std::string culling; + const PropertyTable *props = nullptr; +}; + +class ModelLimbNode : public Model { +public: + ModelLimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~ModelLimbNode(); +}; + +/** DOM class for generic FBX textures */ +class Texture : public Object { +public: + Texture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~Texture(); + + const std::string &Type() const { + return type; + } + + const std::string &FileName() const { + return fileName; + } + + const std::string &RelativeFilename() const { + return relativeFileName; + } + + const std::string &AlphaSource() const { + return alphaSource; + } + + const Vector2 &UVTranslation() const { + return uvTrans; + } + + const Vector2 &UVScaling() const { + return uvScaling; + } + + const PropertyTable *Props() const { + return props; + } + + // return a 4-tuple + const unsigned int *Crop() const { + return crop; + } + + const Video *Media() const { + return media; + } + +private: + Vector2 uvTrans; + Vector2 uvScaling; + + std::string type; + std::string relativeFileName; + std::string fileName; + std::string alphaSource; + const PropertyTable *props = nullptr; + + unsigned int crop[4] = { 0 }; + + const Video *media = nullptr; +}; + +/** DOM class for layered FBX textures */ +class LayeredTexture : public Object { +public: + LayeredTexture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + virtual ~LayeredTexture(); + + // Can only be called after construction of the layered texture object due to construction flag. + void fillTexture(const Document &doc); + + enum BlendMode { + BlendMode_Translucent, + BlendMode_Additive, + BlendMode_Modulate, + BlendMode_Modulate2, + BlendMode_Over, + BlendMode_Normal, + BlendMode_Dissolve, + BlendMode_Darken, + BlendMode_ColorBurn, + BlendMode_LinearBurn, + BlendMode_DarkerColor, + BlendMode_Lighten, + BlendMode_Screen, + BlendMode_ColorDodge, + BlendMode_LinearDodge, + BlendMode_LighterColor, + BlendMode_SoftLight, + BlendMode_HardLight, + BlendMode_VividLight, + BlendMode_LinearLight, + BlendMode_PinLight, + BlendMode_HardMix, + BlendMode_Difference, + BlendMode_Exclusion, + BlendMode_Subtract, + BlendMode_Divide, + BlendMode_Hue, + BlendMode_Saturation, + BlendMode_Color, + BlendMode_Luminosity, + BlendMode_Overlay, + BlendMode_BlendModeCount + }; + + const Texture *getTexture(int index = 0) const { + return textures[index]; + } + int textureCount() const { + return static_cast<int>(textures.size()); + } + BlendMode GetBlendMode() const { + return blendMode; + } + float Alpha() { + return alpha; + } + +private: + std::vector<const Texture *> textures; + BlendMode blendMode; + float alpha; +}; + +typedef std::map<std::string, const Texture *> TextureMap; +typedef std::map<std::string, const LayeredTexture *> LayeredTextureMap; + +/** DOM class for generic FBX videos */ +class Video : public Object { +public: + Video(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~Video(); + + const std::string &Type() const { + return type; + } + + bool IsEmbedded() const { + return contentLength > 0; + } + + const std::string &FileName() const { + return fileName; + } + + const std::string &RelativeFilename() const { + return relativeFileName; + } + + const PropertyTable *Props() const { + return props; + } + + const uint8_t *Content() const { + return content; + } + + uint64_t ContentLength() const { + return contentLength; + } + + uint8_t *RelinquishContent() { + uint8_t *ptr = content; + content = 0; + return ptr; + } + + bool operator==(const Video &other) const { + return ( + type == other.type && relativeFileName == other.relativeFileName && fileName == other.fileName); + } + + bool operator<(const Video &other) const { + return std::tie(type, relativeFileName, fileName) < std::tie(other.type, other.relativeFileName, other.fileName); + } + +private: + std::string type; + std::string relativeFileName; + std::string fileName; + const PropertyTable *props = nullptr; + + uint64_t contentLength = 0; + uint8_t *content = nullptr; +}; + +/** DOM class for generic FBX materials */ +class Material : public Object { +public: + Material(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~Material(); + + const std::string &GetShadingModel() const { + return shading; + } + + bool IsMultilayer() const { + return multilayer; + } + + const PropertyTable *Props() const { + return props; + } + + const TextureMap &Textures() const { + return textures; + } + + const LayeredTextureMap &LayeredTextures() const { + return layeredTextures; + } + +private: + std::string shading; + bool multilayer; + const PropertyTable *props; + + TextureMap textures; + LayeredTextureMap layeredTextures; +}; + +// signed int keys (this can happen!) +typedef std::vector<int64_t> KeyTimeList; +typedef std::vector<float> KeyValueList; + +/** Represents a FBX animation curve (i.e. a 1-dimensional set of keyframes and values therefor) */ +class AnimationCurve : public Object { +public: + AnimationCurve(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); + virtual ~AnimationCurve(); + + /** get list of keyframe positions (time). + * Invariant: |GetKeys()| > 0 */ + const KeyTimeList &GetKeys() const { + return keys; + } + + /** get list of keyframe values. + * Invariant: |GetKeys()| == |GetValues()| && |GetKeys()| > 0*/ + const KeyValueList &GetValues() const { + return values; + } + + const std::map<int64_t, float> &GetValueTimeTrack() const { + return keyvalues; + } + + const std::vector<float> &GetAttributes() const { + return attributes; + } + + const std::vector<unsigned int> &GetFlags() const { + return flags; + } + +private: + KeyTimeList keys; + KeyValueList values; + std::vector<float> attributes; + std::map<int64_t, float> keyvalues; + std::vector<unsigned int> flags; +}; + +/* Typedef for pointers for the animation handler */ +typedef std::shared_ptr<AnimationCurve> AnimationCurvePtr; +typedef std::weak_ptr<AnimationCurve> AnimationCurveWeakPtr; +typedef std::map<std::string, const AnimationCurve *> AnimationMap; + +/* Animation Curve node ptr */ +typedef std::shared_ptr<AnimationCurveNode> AnimationCurveNodePtr; +typedef std::weak_ptr<AnimationCurveNode> AnimationCurveNodeWeakPtr; + +/** Represents a FBX animation curve (i.e. a mapping from single animation curves to nodes) */ +class AnimationCurveNode : public Object { +public: + /* the optional white list specifies a list of property names for which the caller + wants animations for. If the curve node does not match one of these, std::range_error + will be thrown. */ + AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc, + const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0); + + virtual ~AnimationCurveNode(); + + const PropertyTable *Props() const { + return props; + } + + const AnimationMap &Curves() const; + + /** Object the curve is assigned to, this can be NULL if the + * target object has no DOM representation or could not + * be read for other reasons.*/ + Object *Target() const { + return target; + } + + Model *TargetAsModel() const { + return dynamic_cast<Model *>(target); + } + + NodeAttribute *TargetAsNodeAttribute() const { + return dynamic_cast<NodeAttribute *>(target); + } + + /** Property of Target() that is being animated*/ + const std::string &TargetProperty() const { + return prop; + } + +private: + Object *target = nullptr; + const PropertyTable *props; + mutable AnimationMap curves; + std::string prop; + const Document &doc; +}; + +typedef std::vector<const AnimationCurveNode *> AnimationCurveNodeList; + +typedef std::shared_ptr<AnimationLayer> AnimationLayerPtr; +typedef std::weak_ptr<AnimationLayer> AnimationLayerWeakPtr; +typedef std::vector<const AnimationLayer *> AnimationLayerList; + +/** Represents a FBX animation layer (i.e. a list of node animations) */ +class AnimationLayer : public Object { +public: + AnimationLayer(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); + virtual ~AnimationLayer(); + + const PropertyTable *Props() const { + //ai_assert(props.get()); + return props; + } + + /* the optional white list specifies a list of property names for which the caller + wants animations for. Curves not matching this list will not be added to the + animation layer. */ + const AnimationCurveNodeList Nodes(const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0) const; + +private: + const PropertyTable *props; + const Document &doc; +}; + +/** Represents a FBX animation stack (i.e. a list of animation layers) */ +class AnimationStack : public Object { +public: + AnimationStack(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); + virtual ~AnimationStack(); + + fbx_simple_property(LocalStart, int64_t, 0L); + fbx_simple_property(LocalStop, int64_t, 0L); + fbx_simple_property(ReferenceStart, int64_t, 0L); + fbx_simple_property(ReferenceStop, int64_t, 0L); + + const PropertyTable *Props() const { + return props; + } + + const AnimationLayerList &Layers() const { + return layers; + } + +private: + const PropertyTable *props = nullptr; + AnimationLayerList layers; +}; + +/** DOM class for deformers */ +class Deformer : public Object { +public: + Deformer(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + virtual ~Deformer(); + + const PropertyTable *Props() const { + //ai_assert(props.get()); + return props; + } + +private: + const PropertyTable *props; +}; + +/** Constraints are from Maya they can help us with BoneAttachments :) **/ +class Constraint : public Object { +public: + Constraint(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + virtual ~Constraint(); + +private: + const PropertyTable *props; +}; + +typedef std::vector<float> WeightArray; +typedef std::vector<unsigned int> WeightIndexArray; + +/** DOM class for BlendShapeChannel deformers */ +class BlendShapeChannel : public Deformer { +public: + BlendShapeChannel(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~BlendShapeChannel(); + + float DeformPercent() const { + return percent; + } + + const WeightArray &GetFullWeights() const { + return fullWeights; + } + + const std::vector<const ShapeGeometry *> &GetShapeGeometries() const { + return shapeGeometries; + } + +private: + float percent; + WeightArray fullWeights; + std::vector<const ShapeGeometry *> shapeGeometries; +}; + +/** DOM class for BlendShape deformers */ +class BlendShape : public Deformer { +public: + BlendShape(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~BlendShape(); + + const std::vector<const BlendShapeChannel *> &BlendShapeChannels() const { + return blendShapeChannels; + } + +private: + std::vector<const BlendShapeChannel *> blendShapeChannels; +}; + +/** DOM class for skin deformer clusters (aka sub-deformers) */ +class Cluster : public Deformer { +public: + Cluster(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~Cluster(); + + /** get the list of deformer weights associated with this cluster. + * Use #GetIndices() to get the associated vertices. Both arrays + * have the same size (and may also be empty). */ + const std::vector<float> &GetWeights() const { + return weights; + } + + /** get indices into the vertex data of the geometry associated + * with this cluster. Use #GetWeights() to get the associated weights. + * Both arrays have the same size (and may also be empty). */ + const std::vector<unsigned int> &GetIndices() const { + return indices; + } + + /** */ + const Transform &GetTransform() const { + return transform; + } + + const Transform &TransformLink() const { + return transformLink; + } + + const Model *TargetNode() const { + return node; + } + + const Transform &TransformAssociateModel() const { + return transformAssociateModel; + } + + bool TransformAssociateModelValid() const { + return valid_transformAssociateModel; + } + + // property is not in the fbx file + // if the cluster has an associate model + // we then have an additive type + enum SkinLinkMode { + SkinLinkMode_Normalized = 0, + SkinLinkMode_Additive = 1 + }; + + SkinLinkMode GetLinkMode() { + return link_mode; + } + +private: + std::vector<float> weights; + std::vector<unsigned int> indices; + + Transform transform; + Transform transformLink; + Transform transformAssociateModel; + SkinLinkMode link_mode; + bool valid_transformAssociateModel; + const Model *node = nullptr; +}; + +/** DOM class for skin deformers */ +class Skin : public Deformer { +public: + Skin(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); + + virtual ~Skin(); + + float DeformAccuracy() const { + return accuracy; + } + + const std::vector<const Cluster *> &Clusters() const { + return clusters; + } + + enum SkinType { + Skin_Rigid = 0, + Skin_Linear, + Skin_DualQuaternion, + Skin_Blend + }; + + const SkinType &GetSkinType() const { + return skinType; + } + +private: + float accuracy; + SkinType skinType; + std::vector<const Cluster *> clusters; +}; + +/** Represents a link between two FBX objects. */ +class Connection { +public: + Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string &prop, const Document &doc); + ~Connection(); + + // note: a connection ensures that the source and dest objects exist, but + // not that they have DOM representations, so the return value of one of + // these functions can still be NULL. + Object *SourceObject() const; + Object *DestinationObject() const; + + // these, however, are always guaranteed to be valid + LazyObject *LazySourceObject() const; + LazyObject *LazyDestinationObject() const; + + /** return the name of the property the connection is attached to. + * this is an empty string for object to object (OO) connections. */ + const std::string &PropertyName() const { + return prop; + } + + uint64_t InsertionOrder() const { + return insertionOrder; + } + + int CompareTo(const Connection *c) const { + //ai_assert(nullptr != c); + + // note: can't subtract because this would overflow uint64_t + if (InsertionOrder() > c->InsertionOrder()) { + return 1; + } else if (InsertionOrder() < c->InsertionOrder()) { + return -1; + } + return 0; + } + + bool Compare(const Connection *c) const { + //ai_assert(nullptr != c); + + return InsertionOrder() < c->InsertionOrder(); + } + +public: + uint64_t insertionOrder; + const std::string prop; + + uint64_t src, dest; + const Document &doc; +}; + +// XXX again, unique_ptr would be useful. shared_ptr is too +// bloated since the objects have a well-defined single owner +// during their entire lifetime (Document). FBX files have +// up to many thousands of objects (most of which we never use), +// so the memory overhead for them should be kept at a minimum. +typedef std::map<uint64_t, LazyObject *> ObjectMap; +typedef std::map<std::string, const PropertyTable *> PropertyTemplateMap; +typedef std::multimap<uint64_t, const Connection *> ConnectionMap; + +/** DOM class for global document settings, a single instance per document can + * be accessed via Document.Globals(). */ +class FileGlobalSettings { +public: + FileGlobalSettings(const Document &doc, const PropertyTable *props); + + ~FileGlobalSettings(); + + const PropertyTable *Props() const { + return props; + } + + const Document &GetDocument() const { + return doc; + } + + fbx_simple_property(UpAxis, int, 1); + fbx_simple_property(UpAxisSign, int, 1); + fbx_simple_property(FrontAxis, int, 2); + fbx_simple_property(FrontAxisSign, int, 1); + fbx_simple_property(CoordAxis, int, 0); + fbx_simple_property(CoordAxisSign, int, 1); + fbx_simple_property(OriginalUpAxis, int, 0); + fbx_simple_property(OriginalUpAxisSign, int, 1); + fbx_simple_property(UnitScaleFactor, float, 1); + fbx_simple_property(OriginalUnitScaleFactor, float, 1); + fbx_simple_property(AmbientColor, Vector3, Vector3(0, 0, 0)); + fbx_simple_property(DefaultCamera, std::string, ""); + + enum FrameRate { + FrameRate_DEFAULT = 0, + FrameRate_120 = 1, + FrameRate_100 = 2, + FrameRate_60 = 3, + FrameRate_50 = 4, + FrameRate_48 = 5, + FrameRate_30 = 6, + FrameRate_30_DROP = 7, + FrameRate_NTSC_DROP_FRAME = 8, + FrameRate_NTSC_FULL_FRAME = 9, + FrameRate_PAL = 10, + FrameRate_CINEMA = 11, + FrameRate_1000 = 12, + FrameRate_CINEMA_ND = 13, + FrameRate_CUSTOM = 14, + + FrameRate_MAX // end-of-enum sentinel + }; + + fbx_simple_enum_property(TimeMode, FrameRate, FrameRate_DEFAULT); + fbx_simple_property(TimeSpanStart, uint64_t, 0L); + fbx_simple_property(TimeSpanStop, uint64_t, 0L); + fbx_simple_property(CustomFrameRate, float, -1.0f); + +private: + const PropertyTable *props = nullptr; + const Document &doc; +}; + +/** DOM root for a FBX file */ +class Document { +public: + Document(const Parser &parser, const ImportSettings &settings); + + ~Document(); + + LazyObject *GetObject(uint64_t id) const; + + bool IsSafeToImport() const { + return SafeToImport; + } + + bool IsBinary() const { + return parser.IsBinary(); + } + + unsigned int FBXVersion() const { + return fbxVersion; + } + + const std::string &Creator() const { + return creator; + } + + // elements (in this order): Year, Month, Day, Hour, Second, Millisecond + const unsigned int *CreationTimeStamp() const { + return creationTimeStamp; + } + + const FileGlobalSettings *GlobalSettingsPtr() const { + return globals.get(); + } + + const PropertyTable *GetMetadataProperties() const { + return metadata_properties; + } + + const PropertyTemplateMap &Templates() const { + return templates; + } + + const ObjectMap &Objects() const { + return objects; + } + + const ImportSettings &Settings() const { + return settings; + } + + const ConnectionMap &ConnectionsBySource() const { + return src_connections; + } + + const ConnectionMap &ConnectionsByDestination() const { + return dest_connections; + } + + // note: the implicit rule in all DOM classes is to always resolve + // from destination to source (since the FBX object hierarchy is, + // with very few exceptions, a DAG, this avoids cycles). In all + // cases that may involve back-facing edges in the object graph, + // use LazyObject::IsBeingConstructed() to check. + + std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source) const; + std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest) const; + + std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source, const char *classname) const; + std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest, const char *classname) const; + + std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source, + const char *const *classnames, size_t count) const; + std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest, + const char *const *classnames, + size_t count) const; + + const std::vector<const AnimationStack *> &AnimationStacks() const; + const std::vector<uint64_t> &GetAnimationStackIDs() const { + return animationStacks; + } + + const std::vector<uint64_t> &GetConstraintStackIDs() const { + return constraints; + } + + const std::vector<uint64_t> &GetBindPoseIDs() const { + return bind_poses; + }; + + const std::vector<uint64_t> &GetMaterialIDs() const { + return materials; + }; + + const std::vector<uint64_t> &GetSkinIDs() const { + return skins; + } + +private: + std::vector<const Connection *> GetConnectionsSequenced(uint64_t id, const ConnectionMap &) const; + std::vector<const Connection *> GetConnectionsSequenced(uint64_t id, bool is_src, + const ConnectionMap &, + const char *const *classnames, + size_t count) const; + bool ReadHeader(); + void ReadObjects(); + void ReadPropertyTemplates(); + void ReadConnections(); + void ReadGlobalSettings(); + +private: + const ImportSettings &settings; + + ObjectMap objects; + const Parser &parser; + bool SafeToImport = false; + + PropertyTemplateMap templates; + ConnectionMap src_connections; + ConnectionMap dest_connections; + + unsigned int fbxVersion = 0; + std::string creator; + unsigned int creationTimeStamp[7] = { 0 }; + + std::vector<uint64_t> animationStacks; + std::vector<uint64_t> bind_poses; + // constraints aren't in the tree / at least they are not easy to access. + std::vector<uint64_t> constraints; + std::vector<uint64_t> materials; + std::vector<uint64_t> skins; + mutable std::vector<const AnimationStack *> animationStacksResolved; + PropertyTable *metadata_properties = nullptr; + std::shared_ptr<FileGlobalSettings> globals = nullptr; +}; +} // namespace FBXDocParser + +namespace std { +template <> +struct hash<const FBXDocParser::Video> { + std::size_t operator()(const FBXDocParser::Video &video) const { + using std::hash; + using std::size_t; + using std::string; + + size_t res = 17; + res = res * 31 + hash<string>()(video.Name()); + res = res * 31 + hash<string>()(video.RelativeFilename()); + res = res * 31 + hash<string>()(video.Type()); + + return res; + } +}; +} // namespace std + +#endif // FBX_DOCUMENT_H diff --git a/modules/fbx/fbx_parser/FBXDocumentUtil.cpp b/modules/fbx/fbx_parser/FBXDocumentUtil.cpp new file mode 100644 index 0000000000..70b2b3bbe9 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXDocumentUtil.cpp @@ -0,0 +1,172 @@ +/*************************************************************************/ +/* FBXDocumentUtil.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXDocumentUtil.cpp + * @brief Implementation of the FBX DOM utility functions declared in FBXDocumentUtil.h + */ + +#include "FBXDocumentUtil.h" +#include "FBXDocument.h" +#include "FBXParser.h" +#include "FBXProperties.h" +#include "FBXUtil.h" +#include "core/string/print_string.h" + +namespace FBXDocParser { +namespace Util { + +void DOMError(const std::string &message) { + print_error("[FBX-DOM]" + String(message.c_str())); +} + +void DOMError(const std::string &message, const Token *token) { + print_error("[FBX-DOM]" + String(message.c_str()) + ";" + String(token->StringContents().c_str())); +} + +void DOMError(const std::string &message, const std::shared_ptr<Token> token) { + print_error("[FBX-DOM]" + String(message.c_str()) + ";" + String(token->StringContents().c_str())); +} + +void DOMError(const std::string &message, const Element *element /*= NULL*/) { + if (element) { + DOMError(message, element->KeyToken()); + } + print_error("[FBX-DOM] " + String(message.c_str())); +} + +void DOMError(const std::string &message, const std::shared_ptr<Element> element /*= NULL*/) { + if (element) { + DOMError(message, element->KeyToken()); + } + print_error("[FBX-DOM] " + String(message.c_str())); +} + +void DOMWarning(const std::string &message) { + print_verbose("[FBX-DOM] warning:" + String(message.c_str())); +} + +void DOMWarning(const std::string &message, const Token *token) { + print_verbose("[FBX-DOM] warning:" + String(message.c_str()) + ";" + String(token->StringContents().c_str())); +} + +void DOMWarning(const std::string &message, const Element *element /*= NULL*/) { + if (element) { + DOMWarning(message, element->KeyToken()); + return; + } + print_verbose("[FBX-DOM] warning:" + String(message.c_str())); +} + +void DOMWarning(const std::string &message, const std::shared_ptr<Token> token) { + print_verbose("[FBX-DOM] warning:" + String(message.c_str()) + ";" + String(token->StringContents().c_str())); +} + +void DOMWarning(const std::string &message, const std::shared_ptr<Element> element /*= NULL*/) { + if (element) { + DOMWarning(message, element->KeyToken()); + return; + } + print_verbose("[FBX-DOM] warning:" + String(message.c_str())); +} + +// ------------------------------------------------------------------------------------------------ +// fetch a property table and the corresponding property template +const PropertyTable *GetPropertyTable(const Document &doc, + const std::string &templateName, + const ElementPtr element, + const ScopePtr sc, + bool no_warn /*= false*/) { + // todo: make this an abstraction + const ElementPtr Properties70 = sc->GetElement("Properties70"); + const PropertyTable *templateProps = static_cast<const PropertyTable *>(nullptr); + + if (templateName.length()) { + PropertyTemplateMap::const_iterator it = doc.Templates().find(templateName); + if (it != doc.Templates().end()) { + templateProps = (*it).second; + } + } + + if (!Properties70 || !Properties70->Compound()) { + if (!no_warn) { + DOMWarning("property table (Properties70) not found", element); + } + if (templateProps) { + return templateProps; + } else { + return new const PropertyTable(); + } + } + + return new PropertyTable(Properties70, templateProps); +} +} // namespace Util +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXDocumentUtil.h b/modules/fbx/fbx_parser/FBXDocumentUtil.h new file mode 100644 index 0000000000..fb6eb04aea --- /dev/null +++ b/modules/fbx/fbx_parser/FBXDocumentUtil.h @@ -0,0 +1,141 @@ +/*************************************************************************/ +/* FBXDocumentUtil.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2012, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXDocumentUtil.h + * @brief FBX internal utilities used by the DOM reading code + */ +#ifndef FBX_DOCUMENT_UTIL_H +#define FBX_DOCUMENT_UTIL_H + +#include "FBXDocument.h" +#include <memory> +#include <string> + +struct Token; +struct Element; + +namespace FBXDocParser { +namespace Util { + +// Parser errors +void DOMError(const std::string &message); +void DOMError(const std::string &message, const Token *token); +void DOMError(const std::string &message, const Element *element); +void DOMError(const std::string &message, const std::shared_ptr<Element> element); +void DOMError(const std::string &message, const std::shared_ptr<Token> token); + +// Parser warnings +void DOMWarning(const std::string &message); +void DOMWarning(const std::string &message, const Token *token); +void DOMWarning(const std::string &message, const Element *element); +void DOMWarning(const std::string &message, const std::shared_ptr<Token> token); +void DOMWarning(const std::string &message, const std::shared_ptr<Element> element); + +// fetch a property table and the corresponding property template +const PropertyTable *GetPropertyTable(const Document &doc, + const std::string &templateName, + const ElementPtr element, + const ScopePtr sc, + bool no_warn = false); + +// ------------------------------------------------------------------------------------------------ +template <typename T> +const T *ProcessSimpleConnection(const Connection &con, + bool is_object_property_conn, + const char *name, + const ElementPtr element, + const char **propNameOut = nullptr) { + if (is_object_property_conn && !con.PropertyName().length()) { + DOMWarning("expected incoming " + std::string(name) + + " link to be an object-object connection, ignoring", + element); + return nullptr; + } else if (!is_object_property_conn && con.PropertyName().length()) { + DOMWarning("expected incoming " + std::string(name) + + " link to be an object-property connection, ignoring", + element); + return nullptr; + } + + if (is_object_property_conn && propNameOut) { + // note: this is ok, the return value of PropertyValue() is guaranteed to + // remain valid and unchanged as long as the document exists. + *propNameOut = con.PropertyName().c_str(); + } + + // Cast Object to AnimationPlayer for example using safe functions, which return nullptr etc + Object *ob = con.SourceObject(); + ERR_FAIL_COND_V_MSG(!ob, nullptr, "Failed to load object from SourceObject ptr"); + return dynamic_cast<const T *>(ob); +} +} // namespace Util +} // namespace FBXDocParser + +#endif // FBX_DOCUMENT_UTIL_H diff --git a/modules/fbx/fbx_parser/FBXImportSettings.h b/modules/fbx/fbx_parser/FBXImportSettings.h new file mode 100644 index 0000000000..4abca6b990 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXImportSettings.h @@ -0,0 +1,173 @@ +/*************************************************************************/ +/* FBXImportSettings.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXImportSettings.h + * @brief FBX importer runtime configuration + */ +#ifndef FBX_IMPORT_SETTINGS_H +#define FBX_IMPORT_SETTINGS_H + +namespace FBXDocParser { + +/** FBX import settings, parts of which are publicly accessible via their corresponding AI_CONFIG constants */ +struct ImportSettings { + ImportSettings() : + strictMode(true), readAllLayers(true), readAllMaterials(true), readMaterials(true), readTextures(true), readCameras(true), readLights(true), readAnimations(true), readWeights(true), preservePivots(true), optimizeEmptyAnimationCurves(true), useLegacyEmbeddedTextureNaming(false), removeEmptyBones(true), convertToMeters(false) { + // empty + } + + /** enable strict mode: + * - only accept fbx 2012, 2013 files + * - on the slightest error, give up. + * + * Basically, strict mode means that the fbx file will actually + * be validated. Strict mode is off by default. */ + bool strictMode; + + /** specifies whether all geometry layers are read and scanned for + * usable data channels. The FBX spec indicates that many readers + * will only read the first channel and that this is in some way + * the recommended way- in reality, however, it happens a lot that + * vertex data is spread among multiple layers. The default + * value for this option is true.*/ + bool readAllLayers; + + /** specifies whether all materials are read, or only those that + * are referenced by at least one mesh. Reading all materials + * may make FBX reading a lot slower since all objects + * need to be processed . + * This bit is ignored unless readMaterials=true*/ + bool readAllMaterials; + + /** import materials (true) or skip them and assign a default + * material. The default value is true.*/ + bool readMaterials; + + /** import embedded textures? Default value is true.*/ + bool readTextures; + + /** import cameras? Default value is true.*/ + bool readCameras; + + /** import light sources? Default value is true.*/ + bool readLights; + + /** import animations (i.e. animation curves, the node + * skeleton is always imported). Default value is true. */ + bool readAnimations; + + /** read bones (vertex weights and deform info). + * Default value is true. */ + bool readWeights; + + /** preserve transformation pivots and offsets. Since these can + * not directly be represented in assimp, additional dummy + * nodes will be generated. Note that settings this to false + * can make animation import a lot slower. The default value + * is true. + * + * The naming scheme for the generated nodes is: + * <OriginalName>_$AssimpFbx$_<TransformName> + * + * where <TransformName> is one of + * RotationPivot + * RotationOffset + * PreRotation + * PostRotation + * ScalingPivot + * ScalingOffset + * Translation + * Scaling + * Rotation + **/ + bool preservePivots; + + /** do not import animation curves that specify a constant + * values matching the corresponding node transformation. + * The default value is true. */ + bool optimizeEmptyAnimationCurves; + + /** use legacy naming for embedded textures eg: (*0, *1, *2) + */ + bool useLegacyEmbeddedTextureNaming; + + /** Empty bones shall be removed + */ + bool removeEmptyBones; + + /** Set to true to perform a conversion from cm to meter after the import + */ + bool convertToMeters; +}; +} // namespace FBXDocParser + +#endif // FBX_IMPORT_SETTINGS_H diff --git a/modules/fbx/fbx_parser/FBXMaterial.cpp b/modules/fbx/fbx_parser/FBXMaterial.cpp new file mode 100644 index 0000000000..5d25809d7b --- /dev/null +++ b/modules/fbx/fbx_parser/FBXMaterial.cpp @@ -0,0 +1,407 @@ +/*************************************************************************/ +/* FBXMaterial.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXMaterial.cpp + * @brief Assimp::FBX::Material and Assimp::FBX::Texture implementation + */ + +#include "ByteSwapper.h" +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXImportSettings.h" +#include "FBXParser.h" +#include "FBXProperties.h" + +#include "FBXUtil.h" +#include <algorithm> // std::transform + +namespace FBXDocParser { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Material::Material(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Object(id, element, name) { + const ScopePtr sc = GetRequiredScope(element); + + const ElementPtr ShadingModel = sc->GetElement("ShadingModel"); + const ElementPtr MultiLayer = sc->GetElement("MultiLayer"); + + if (MultiLayer) { + multilayer = !!ParseTokenAsInt(GetRequiredToken(MultiLayer, 0)); + } + + if (ShadingModel) { + shading = ParseTokenAsString(GetRequiredToken(ShadingModel, 0)); + } else { + DOMWarning("shading mode not specified, assuming phong", element); + shading = "phong"; + } + + std::string templateName; + + if (shading == "phong") { + templateName = "Material.Phong"; + } else if (shading == "lambert") { + templateName = "Material.Lambert"; + } else if (shading == "unknown") { + templateName = "Material.StingRay"; + } else { + DOMWarning("shading mode not recognized: " + shading, element); + } + + props = GetPropertyTable(doc, templateName, element, sc); + + // resolve texture links + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID()); + for (const Connection *con : conns) { + // texture link to properties, not objects + if (!con->PropertyName().length()) { + continue; + } + + Object *ob = con->SourceObject(); + if (!ob) { + DOMWarning("failed to read source object for texture link, ignoring", element); + continue; + } + + const Texture *tex = dynamic_cast<const Texture *>(ob); + if (!tex) { + LayeredTexture *layeredTexture = dynamic_cast<LayeredTexture *>(ob); + + if (!layeredTexture) { + DOMWarning("source object for texture link is not a texture or layered texture, ignoring", element); + continue; + } + + const std::string &prop = con->PropertyName(); + if (layeredTextures.find(prop) != layeredTextures.end()) { + DOMWarning("duplicate layered texture link: " + prop, element); + } + + layeredTextures[prop] = layeredTexture; + layeredTexture->fillTexture(doc); + } else { + const std::string &prop = con->PropertyName(); + if (textures.find(prop) != textures.end()) { + DOMWarning("duplicate texture link: " + prop, element); + } + + textures[prop] = tex; + } + } +} + +// ------------------------------------------------------------------------------------------------ +Material::~Material() { + if (props != nullptr) { + delete props; + props = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +Texture::Texture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Object(id, element, name), uvScaling(1.0f, 1.0f), media(nullptr) { + const ScopePtr sc = GetRequiredScope(element); + + const ElementPtr Type = sc->GetElement("Type"); + const ElementPtr FileName = sc->GetElement("FileName"); + const ElementPtr RelativeFilename = sc->GetElement("RelativeFilename"); + const ElementPtr ModelUVTranslation = sc->GetElement("ModelUVTranslation"); + const ElementPtr ModelUVScaling = sc->GetElement("ModelUVScaling"); + const ElementPtr Texture_Alpha_Source = sc->GetElement("Texture_Alpha_Source"); + const ElementPtr Cropping = sc->GetElement("Cropping"); + + if (Type) { + type = ParseTokenAsString(GetRequiredToken(Type, 0)); + } + + if (FileName) { + fileName = ParseTokenAsString(GetRequiredToken(FileName, 0)); + } + + if (RelativeFilename) { + relativeFileName = ParseTokenAsString(GetRequiredToken(RelativeFilename, 0)); + } + + if (ModelUVTranslation) { + uvTrans = Vector2(ParseTokenAsFloat(GetRequiredToken(ModelUVTranslation, 0)), + ParseTokenAsFloat(GetRequiredToken(ModelUVTranslation, 1))); + } + + if (ModelUVScaling) { + uvScaling = Vector2(ParseTokenAsFloat(GetRequiredToken(ModelUVScaling, 0)), + ParseTokenAsFloat(GetRequiredToken(ModelUVScaling, 1))); + } + + if (Cropping) { + crop[0] = ParseTokenAsInt(GetRequiredToken(Cropping, 0)); + crop[1] = ParseTokenAsInt(GetRequiredToken(Cropping, 1)); + crop[2] = ParseTokenAsInt(GetRequiredToken(Cropping, 2)); + crop[3] = ParseTokenAsInt(GetRequiredToken(Cropping, 3)); + } else { + // vc8 doesn't support the crop() syntax in initialization lists + // (and vc9 WARNS about the new (i.e. compliant) behaviour). + crop[0] = crop[1] = crop[2] = crop[3] = 0; + } + + if (Texture_Alpha_Source) { + alphaSource = ParseTokenAsString(GetRequiredToken(Texture_Alpha_Source, 0)); + } + + props = GetPropertyTable(doc, "Texture.FbxFileTexture", element, sc); + + // 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available. + bool ok; + const Vector3 &scaling = PropertyGet<Vector3>(props, "Scaling", ok); + if (ok) { + uvScaling.x = scaling.x; + uvScaling.y = scaling.y; + } + + const Vector3 &trans = PropertyGet<Vector3>(props, "Translation", ok); + if (ok) { + uvTrans.x = trans.x; + uvTrans.y = trans.y; + } + + // resolve video links + if (doc.Settings().readTextures) { + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID()); + for (const Connection *con : conns) { + const Object *const ob = con->SourceObject(); + if (!ob) { + DOMWarning("failed to read source object for texture link, ignoring", element); + continue; + } + + const Video *const video = dynamic_cast<const Video *>(ob); + if (video) { + media = video; + } + } + } +} + +Texture::~Texture() { + if (props != nullptr) { + delete props; + props = nullptr; + } +} + +LayeredTexture::LayeredTexture(uint64_t id, const ElementPtr element, const Document & /*doc*/, const std::string &name) : + Object(id, element, name), blendMode(BlendMode_Modulate), alpha(1) { + const ScopePtr sc = GetRequiredScope(element); + + ElementPtr BlendModes = sc->GetElement("BlendModes"); + ElementPtr Alphas = sc->GetElement("Alphas"); + + if (BlendModes != 0) { + blendMode = (BlendMode)ParseTokenAsInt(GetRequiredToken(BlendModes, 0)); + } + if (Alphas != 0) { + alpha = ParseTokenAsFloat(GetRequiredToken(Alphas, 0)); + } +} + +LayeredTexture::~LayeredTexture() { +} + +void LayeredTexture::fillTexture(const Document &doc) { + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID()); + for (size_t i = 0; i < conns.size(); ++i) { + const Connection *con = conns.at(i); + + const Object *const ob = con->SourceObject(); + if (!ob) { + DOMWarning("failed to read source object for texture link, ignoring", element); + continue; + } + + const Texture *const tex = dynamic_cast<const Texture *>(ob); + + textures.push_back(tex); + } +} + +// ------------------------------------------------------------------------------------------------ +Video::Video(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Object(id, element, name), contentLength(0), content(0) { + const ScopePtr sc = GetRequiredScope(element); + + const ElementPtr Type = sc->GetElement("Type"); + // File Version 7500 Crashes if this is not checked fully. + // As of writing this comment 7700 exists, in August 2020 + ElementPtr FileName = nullptr; + if (HasElement(sc, "Filename")) { + FileName = (ElementPtr)sc->GetElement("Filename"); + } else if (HasElement(sc, "FileName")) { + FileName = (ElementPtr)sc->GetElement("FileName"); + } else { + print_error("file has invalid video material returning..."); + return; + } + const ElementPtr RelativeFilename = sc->GetElement("RelativeFilename"); + const ElementPtr Content = sc->GetElement("Content"); + + if (Type) { + type = ParseTokenAsString(GetRequiredToken(Type, 0)); + } + + if (FileName) { + fileName = ParseTokenAsString(GetRequiredToken(FileName, 0)); + } + + if (RelativeFilename) { + relativeFileName = ParseTokenAsString(GetRequiredToken(RelativeFilename, 0)); + } + + if (Content && !Content->Tokens().empty()) { + //this field is omitted when the embedded texture is already loaded, let's ignore if it's not found + try { + const Token *token = GetRequiredToken(Content, 0); + const char *data = token->begin(); + if (!token->IsBinary()) { + if (*data != '"') { + DOMError("embedded content is not surrounded by quotation marks", element); + } else { + size_t targetLength = 0; + auto numTokens = Content->Tokens().size(); + // First time compute size (it could be large like 64Gb and it is good to allocate it once) + for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) { + const Token *dataToken = GetRequiredToken(Content, tokenIdx); + size_t tokenLength = dataToken->end() - dataToken->begin() - 2; // ignore double quotes + const char *base64data = dataToken->begin() + 1; + const size_t outLength = Util::ComputeDecodedSizeBase64(base64data, tokenLength); + if (outLength == 0) { + DOMError("Corrupted embedded content found", element); + } + targetLength += outLength; + } + if (targetLength == 0) { + DOMError("Corrupted embedded content found", element); + } else { + content = new uint8_t[targetLength]; + contentLength = static_cast<uint64_t>(targetLength); + size_t dst_offset = 0; + for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) { + const Token *dataToken = GetRequiredToken(Content, tokenIdx); + ERR_FAIL_COND(!dataToken); + size_t tokenLength = dataToken->end() - dataToken->begin() - 2; // ignore double quotes + const char *base64data = dataToken->begin() + 1; + dst_offset += Util::DecodeBase64(base64data, tokenLength, content + dst_offset, targetLength - dst_offset); + } + if (targetLength != dst_offset) { + delete[] content; + contentLength = 0; + DOMError("Corrupted embedded content found", element); + } + } + } + } else if (static_cast<size_t>(token->end() - data) < 5) { + DOMError("binary data array is too short, need five (5) bytes for type signature and element count", element); + } else if (*data != 'R') { + DOMWarning("video content is not raw binary data, ignoring", element); + } else { + // read number of elements + uint32_t len = 0; + ::memcpy(&len, data + 1, sizeof(len)); + AI_SWAP4(len); + + contentLength = len; + + content = new uint8_t[len]; + ::memcpy(content, data + 5, len); + } + } catch (...) { + // //we don't need the content data for contents that has already been loaded + // ASSIMP_LOG_VERBOSE_DEBUG_F("Caught exception in FBXMaterial (likely because content was already loaded): ", + // runtimeError.what()); + } + } + + props = GetPropertyTable(doc, "Video.FbxVideo", element, sc); +} + +Video::~Video() { + if (content) { + delete[] content; + } + + if (props != nullptr) { + delete props; + props = nullptr; + } +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXMeshGeometry.cpp b/modules/fbx/fbx_parser/FBXMeshGeometry.cpp new file mode 100644 index 0000000000..4fa5fc0ac9 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXMeshGeometry.cpp @@ -0,0 +1,485 @@ +/*************************************************************************/ +/* FBXMeshGeometry.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXMeshGeometry.cpp + * @brief Assimp::FBX::MeshGeometry implementation + */ + +#include <functional> + +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXImportSettings.h" +#include "FBXMeshGeometry.h" +#include "core/math/vector3.h" + +namespace FBXDocParser { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Geometry::Geometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : + Object(id, element, name), skin() { + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer"); + for (const Connection *con : conns) { + const Skin *sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element); + if (sk) { + skin = sk; + } + const BlendShape *bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry", + element); + if (bsp) { + blendShapes.push_back(bsp); + } + } +} + +// ------------------------------------------------------------------------------------------------ +Geometry::~Geometry() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<const BlendShape *> &Geometry::get_blend_shapes() const { + return blendShapes; +} + +// ------------------------------------------------------------------------------------------------ +const Skin *Geometry::DeformerSkin() const { + return skin; +} + +// ------------------------------------------------------------------------------------------------ +MeshGeometry::MeshGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : + Geometry(id, element, name, doc) { + print_verbose("mesh name: " + String(name.c_str())); + + ScopePtr sc = element->Compound(); + ERR_FAIL_COND_MSG(sc == nullptr, "failed to read geometry, prevented crash"); + ERR_FAIL_COND_MSG(!HasElement(sc, "Vertices"), "Detected mesh with no vertexes, didn't populate the mesh"); + + // must have Mesh elements: + const ElementPtr Vertices = GetRequiredElement(sc, "Vertices", element); + const ElementPtr PolygonVertexIndex = GetRequiredElement(sc, "PolygonVertexIndex", element); + + if (HasElement(sc, "Edges")) { + const ElementPtr element_edges = GetRequiredElement(sc, "Edges", element); + ParseVectorDataArray(m_edges, element_edges); + } + + // read mesh data into arrays + ParseVectorDataArray(m_vertices, Vertices); + ParseVectorDataArray(m_face_indices, PolygonVertexIndex); + + ERR_FAIL_COND_MSG(m_vertices.empty(), "mesh with no vertexes in FBX file, did you mean to delete it?"); + ERR_FAIL_COND_MSG(m_face_indices.empty(), "mesh has no faces, was this intended?"); + + // Retrieve layer elements, for all of the mesh + const ElementCollection &Layer = sc->GetCollection("Layer"); + + // Store all layers + std::vector<std::tuple<int, std::string>> valid_layers; + + // now read the sub mesh information from the geometry (normals, uvs, etc) + for (ElementMap::const_iterator it = Layer.first; it != Layer.second; ++it) { + const ScopePtr layer = GetRequiredScope(it->second); + const ElementCollection &LayerElement = layer->GetCollection("LayerElement"); + for (ElementMap::const_iterator eit = LayerElement.first; eit != LayerElement.second; ++eit) { + std::string layer_name = eit->first; + ElementPtr element_layer = eit->second; + const ScopePtr layer_element = GetRequiredScope(element_layer); + + // Actual usable 'type' LayerElementUV, LayerElementNormal, etc + const ElementPtr Type = GetRequiredElement(layer_element, "Type"); + const ElementPtr TypedIndex = GetRequiredElement(layer_element, "TypedIndex"); + const std::string &type = ParseTokenAsString(GetRequiredToken(Type, 0)); + const int typedIndex = ParseTokenAsInt(GetRequiredToken(TypedIndex, 0)); + + // we only need the layer name and the typed index. + valid_layers.push_back(std::tuple<int, std::string>(typedIndex, type)); + } + } + + // get object / mesh directly from the FBX by the element ID. + const ScopePtr top = GetRequiredScope(element); + + // iterate over all layers for the mesh (uvs, normals, smoothing groups, colors, etc) + for (size_t x = 0; x < valid_layers.size(); x++) { + const int layer_id = std::get<0>(valid_layers[x]); + const std::string &layer_type_name = std::get<1>(valid_layers[x]); + + // Get collection of elements from the XLayerMap (example: LayerElementUV) + // this must contain our proper elements. + + // This is stupid, because it means we select them ALL not just the one we want. + // but it's fine we can match by id. + + const ElementCollection &candidates = top->GetCollection(layer_type_name); + + ElementMap::const_iterator iter; + for (iter = candidates.first; iter != candidates.second; ++iter) { + const ScopePtr layer_scope = GetRequiredScope(iter->second); + TokenPtr layer_token = GetRequiredToken(iter->second, 0); + const int index = ParseTokenAsInt(layer_token); + + ERR_FAIL_COND_MSG(layer_scope == nullptr, "prevented crash, layer scope is invalid"); + + if (index == layer_id) { + const std::string &MappingInformationType = ParseTokenAsString(GetRequiredToken( + GetRequiredElement(layer_scope, "MappingInformationType"), 0)); + + const std::string &ReferenceInformationType = ParseTokenAsString(GetRequiredToken( + GetRequiredElement(layer_scope, "ReferenceInformationType"), 0)); + + if (layer_type_name == "LayerElementUV") { + if (index == 0) { + m_uv_0 = resolve_vertex_data_array<Vector2>(layer_scope, MappingInformationType, ReferenceInformationType, "UV"); + } else if (index == 1) { + m_uv_1 = resolve_vertex_data_array<Vector2>(layer_scope, MappingInformationType, ReferenceInformationType, "UV"); + } + } else if (layer_type_name == "LayerElementMaterial") { + m_material_allocation_ids = resolve_vertex_data_array<int>(layer_scope, MappingInformationType, ReferenceInformationType, "Materials"); + } else if (layer_type_name == "LayerElementNormal") { + m_normals = resolve_vertex_data_array<Vector3>(layer_scope, MappingInformationType, ReferenceInformationType, "Normals"); + } else if (layer_type_name == "LayerElementColor") { + m_colors = resolve_vertex_data_array<Color>(layer_scope, MappingInformationType, ReferenceInformationType, "Colors", "ColorIndex"); + // NOTE: this is a useful sanity check to ensure you're getting any color data which is not default. + // const Color first_color_check = m_colors.data[0]; + // bool colors_are_all_the_same = true; + // size_t i = 1; + // for(i = 1; i < m_colors.data.size(); i++) + // { + // const Color current_color = m_colors.data[i]; + // if(current_color.is_equal_approx(first_color_check)) + // { + // continue; + // } + // else + // { + // colors_are_all_the_same = false; + // break; + // } + // } + // + // if(colors_are_all_the_same) + // { + // print_error("Color serialisation is not working for vertex colors some should be different in the test asset."); + // } + // else + // { + // print_verbose("Color array has unique colors at index: " + itos(i)); + // } + } + } + } + } + + print_verbose("Mesh statistics \nuv_0: " + m_uv_0.debug_info() + "\nuv_1: " + m_uv_1.debug_info() + "\nvertices: " + itos(m_vertices.size())); + + // Compose the edge of the mesh. + // You can see how the edges are stored into the FBX here: https://gist.github.com/AndreaCatania/da81840f5aa3b2feedf189e26c5a87e6 + for (size_t i = 0; i < m_edges.size(); i += 1) { + ERR_FAIL_INDEX_MSG((size_t)m_edges[i], m_face_indices.size(), "The edge is pointing to a weird location in the face indices. The FBX is corrupted."); + int polygon_vertex_0 = m_face_indices[m_edges[i]]; + int polygon_vertex_1; + if (polygon_vertex_0 < 0) { + // The polygon_vertex_0 points to the end of a polygon, so it's + // connected with the beginning of polygon in the edge list. + + // Fist invert the vertex. + polygon_vertex_0 = ~polygon_vertex_0; + + // Search the start vertex of the polygon. + // Iterate from the polygon_vertex_index backward till the start of + // the polygon is found. + ERR_FAIL_COND_MSG(m_edges[i] - 1 < 0, "The polygon is not yet started and we already need the final vertex. This FBX is corrupted."); + bool found_it = false; + for (int x = m_edges[i] - 1; x >= 0; x -= 1) { + if (x == 0) { + // This for sure is the start. + polygon_vertex_1 = m_face_indices[x]; + found_it = true; + break; + } else if (m_face_indices[x] < 0) { + // This is the end of the previous polygon, so the next is + // the start of the polygon we need. + polygon_vertex_1 = m_face_indices[x + 1]; + found_it = true; + break; + } + } + // As the algorithm above, this check is useless. Because the first + // ever vertex is always considered the begining of a polygon. + ERR_FAIL_COND_MSG(found_it == false, "Was not possible to find the first vertex of this polygon. FBX file is corrupted."); + + } else { + ERR_FAIL_INDEX_MSG((size_t)(m_edges[i] + 1), m_face_indices.size(), "FBX The other FBX edge seems to point to an invalid vertices. This FBX file is corrupted."); + // Take the next vertex + polygon_vertex_1 = m_face_indices[m_edges[i] + 1]; + } + + if (polygon_vertex_1 < 0) { + // We don't care if the `polygon_vertex_1` is the end of the polygon, + // for `polygon_vertex_1` so we can just invert it. + polygon_vertex_1 = ~polygon_vertex_1; + } + + ERR_FAIL_COND_MSG(polygon_vertex_0 == polygon_vertex_1, "The vertices of this edge can't be the same, Is this a point???. This FBX file is corrupted."); + + // Just create the edge. + edge_map.push_back({ polygon_vertex_0, polygon_vertex_1 }); + } +} + +MeshGeometry::~MeshGeometry() { + // empty +} + +const std::vector<Vector3> &MeshGeometry::get_vertices() const { + return m_vertices; +} + +const std::vector<MeshGeometry::Edge> &MeshGeometry::get_edge_map() const { + return edge_map; +} + +const std::vector<int> &MeshGeometry::get_polygon_indices() const { + return m_face_indices; +} + +const std::vector<int> &MeshGeometry::get_edges() const { + return m_edges; +} + +const MeshGeometry::MappingData<Vector3> &MeshGeometry::get_normals() const { + return m_normals; +} + +const MeshGeometry::MappingData<Vector2> &MeshGeometry::get_uv_0() const { + //print_verbose("get uv_0 " + m_uv_0.debug_info() ); + return m_uv_0; +} + +const MeshGeometry::MappingData<Vector2> &MeshGeometry::get_uv_1() const { + //print_verbose("get uv_1 " + m_uv_1.debug_info() ); + return m_uv_1; +} + +const MeshGeometry::MappingData<Color> &MeshGeometry::get_colors() const { + return m_colors; +} + +const MeshGeometry::MappingData<int> &MeshGeometry::get_material_allocation_id() const { + return m_material_allocation_ids; +} + +int MeshGeometry::get_edge_id(const std::vector<Edge> &p_map, int p_vertex_a, int p_vertex_b) { + for (size_t i = 0; i < p_map.size(); i += 1) { + if ((p_map[i].vertex_0 == p_vertex_a && p_map[i].vertex_1 == p_vertex_b) || (p_map[i].vertex_1 == p_vertex_a && p_map[i].vertex_0 == p_vertex_b)) { + return i; + } + } + return -1; +} + +MeshGeometry::Edge MeshGeometry::get_edge(const std::vector<Edge> &p_map, int p_id) { + ERR_FAIL_INDEX_V_MSG((size_t)p_id, p_map.size(), Edge({ -1, -1 }), "ID not found."); + return p_map[p_id]; +} + +template <class T> +MeshGeometry::MappingData<T> MeshGeometry::resolve_vertex_data_array( + const ScopePtr source, + const std::string &MappingInformationType, + const std::string &ReferenceInformationType, + const std::string &dataElementName, + const std::string &indexOverride) { + ERR_FAIL_COND_V_MSG(source == nullptr, MappingData<T>(), "Invalid scope operator preventing memory corruption"); + + // UVIndex, MaterialIndex, NormalIndex, etc.. + std::string indexDataElementName; + + if (indexOverride != "") { + // Colors should become ColorIndex + indexDataElementName = indexOverride; + } else { + // Some indexes will exist. + indexDataElementName = dataElementName + "Index"; + } + + // goal: expand everything to be per vertex + + ReferenceType l_ref_type = ReferenceType::direct; + + // Read the reference type into the enumeration + if (ReferenceInformationType == "IndexToDirect") { + l_ref_type = ReferenceType::index_to_direct; + } else if (ReferenceInformationType == "Index") { + // set non legacy index to direct mapping + l_ref_type = ReferenceType::index; + } else if (ReferenceInformationType == "Direct") { + l_ref_type = ReferenceType::direct; + } else { + ERR_FAIL_V_MSG(MappingData<T>(), "invalid reference type has the FBX format changed?"); + } + + MapType l_map_type = MapType::none; + + if (MappingInformationType == "None") { + l_map_type = MapType::none; + } else if (MappingInformationType == "ByVertice") { + l_map_type = MapType::vertex; + } else if (MappingInformationType == "ByPolygonVertex") { + l_map_type = MapType::polygon_vertex; + } else if (MappingInformationType == "ByPolygon") { + l_map_type = MapType::polygon; + } else if (MappingInformationType == "ByEdge") { + l_map_type = MapType::edge; + } else if (MappingInformationType == "AllSame") { + l_map_type = MapType::all_the_same; + } else { + print_error("invalid mapping type: " + String(MappingInformationType.c_str())); + } + + // create mapping data + MeshGeometry::MappingData<T> tempData; + tempData.map_type = l_map_type; + tempData.ref_type = l_ref_type; + + // parse data into array + ParseVectorDataArray(tempData.data, GetRequiredElement(source, dataElementName)); + + // index array wont always exist + const ElementPtr element = GetOptionalElement(source, indexDataElementName); + if (element) { + ParseVectorDataArray(tempData.index, element); + } + + return tempData; +} +// ------------------------------------------------------------------------------------------------ +ShapeGeometry::ShapeGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : + Geometry(id, element, name, doc) { + const ScopePtr sc = element->Compound(); + if (nullptr == sc) { + DOMError("failed to read Geometry object (class: Shape), no data scope found"); + } + const ElementPtr Indexes = GetRequiredElement(sc, "Indexes", element); + const ElementPtr Normals = GetRequiredElement(sc, "Normals", element); + const ElementPtr Vertices = GetRequiredElement(sc, "Vertices", element); + ParseVectorDataArray(m_indices, Indexes); + ParseVectorDataArray(m_vertices, Vertices); + ParseVectorDataArray(m_normals, Normals); +} + +// ------------------------------------------------------------------------------------------------ +ShapeGeometry::~ShapeGeometry() { + // empty +} +// ------------------------------------------------------------------------------------------------ +const std::vector<Vector3> &ShapeGeometry::GetVertices() const { + return m_vertices; +} +// ------------------------------------------------------------------------------------------------ +const std::vector<Vector3> &ShapeGeometry::GetNormals() const { + return m_normals; +} +// ------------------------------------------------------------------------------------------------ +const std::vector<unsigned int> &ShapeGeometry::GetIndices() const { + return m_indices; +} +// ------------------------------------------------------------------------------------------------ +LineGeometry::LineGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : + Geometry(id, element, name, doc) { + const ScopePtr sc = element->Compound(); + if (!sc) { + DOMError("failed to read Geometry object (class: Line), no data scope found"); + } + const ElementPtr Points = GetRequiredElement(sc, "Points", element); + const ElementPtr PointsIndex = GetRequiredElement(sc, "PointsIndex", element); + ParseVectorDataArray(m_vertices, Points); + ParseVectorDataArray(m_indices, PointsIndex); +} + +// ------------------------------------------------------------------------------------------------ +LineGeometry::~LineGeometry() { + // empty +} +// ------------------------------------------------------------------------------------------------ +const std::vector<Vector3> &LineGeometry::GetVertices() const { + return m_vertices; +} +// ------------------------------------------------------------------------------------------------ +const std::vector<int> &LineGeometry::GetIndices() const { + return m_indices; +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXMeshGeometry.h b/modules/fbx/fbx_parser/FBXMeshGeometry.h new file mode 100644 index 0000000000..666da46edd --- /dev/null +++ b/modules/fbx/fbx_parser/FBXMeshGeometry.h @@ -0,0 +1,263 @@ +/*************************************************************************/ +/* FBXMeshGeometry.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef FBX_MESH_GEOMETRY_H +#define FBX_MESH_GEOMETRY_H + +#include "core/math/color.h" +#include "core/math/vector2.h" +#include "core/math/vector3.h" +#include "core/templates/vector.h" + +#include "FBXDocument.h" +#include "FBXParser.h" + +#include <iostream> + +#define AI_MAX_NUMBER_OF_TEXTURECOORDS 4 +#define AI_MAX_NUMBER_OF_COLOR_SETS 8 + +namespace FBXDocParser { + +/* + * DOM base class for all kinds of FBX geometry + */ +class Geometry : public Object { +public: + Geometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); + virtual ~Geometry(); + + /** Get the Skin attached to this geometry or NULL */ + const Skin *DeformerSkin() const; + + const std::vector<const BlendShape *> &get_blend_shapes() const; + + size_t get_blend_shape_count() const { + return blendShapes.size(); + } + +private: + const Skin *skin = nullptr; + std::vector<const BlendShape *> blendShapes; +}; + +typedef std::vector<int> MatIndexArray; + +/// Map Geometry stores the FBX file information. +/// +/// # FBX doc. +/// ## Reference type declared: +/// - Direct (directly related to the mapping information type) +/// - IndexToDirect (Map with key value, meaning depends on the MappingInformationType) +/// +/// ## Map Type: +/// * None The mapping is undetermined. +/// * ByVertex There will be one mapping coordinate for each surface control point/vertex (ControlPoint is a vertex). +/// * If you have direct reference type verticies[x] +/// * If you have IndexToDirect reference type the UV +/// * ByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part. (Sorted by polygon, referencing vertex) +/// * ByPolygon There can be only one mapping coordinate for the whole polygon. +/// * One mapping per polygon polygon x has this normal x +/// * For each vertex of the polygon then set the normal to x +/// * ByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements. (Mapping is referencing the edge id) +/// * AllSame There can be only one mapping coordinate for the whole surface. +class MeshGeometry : public Geometry { +public: + enum class MapType { + none = 0, // No mapping type. Stored as "None". + vertex, // Maps per vertex. Stored as "ByVertice". + polygon_vertex, // Maps per polygon vertex. Stored as "ByPolygonVertex". + polygon, // Maps per polygon. Stored as "ByPolygon". + edge, // Maps per edge. Stored as "ByEdge". + all_the_same // Uaps to everything. Stored as "AllSame". + }; + + enum class ReferenceType { + direct = 0, + index = 1, + index_to_direct = 2 + }; + + template <class T> + struct MappingData { + MapType map_type = MapType::none; + ReferenceType ref_type = ReferenceType::direct; + std::vector<T> data; + /// The meaning of the indices depends from the `MapType`. + /// If `ref_type` is `direct` this map is hollow. + std::vector<int> index; + + String debug_info() const { + return "indexes: " + itos(index.size()) + " data: " + itos(data.size()); + } + }; + + struct Edge { + int vertex_0 = 0, vertex_1 = 0; + Edge(int v0, int v1) : + vertex_0(v0), vertex_1(v1) {} + Edge() {} + }; + +public: + MeshGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); + + virtual ~MeshGeometry(); + + const std::vector<Vector3> &get_vertices() const; + const std::vector<Edge> &get_edge_map() const; + const std::vector<int> &get_polygon_indices() const; + const std::vector<int> &get_edges() const; + const MappingData<Vector3> &get_normals() const; + const MappingData<Vector2> &get_uv_0() const; + const MappingData<Vector2> &get_uv_1() const; + const MappingData<Color> &get_colors() const; + const MappingData<int> &get_material_allocation_id() const; + + /// Returns -1 if the vertices doesn't form an edge. Vertex order, doesn't + // matter. + static int get_edge_id(const std::vector<Edge> &p_map, int p_vertex_a, int p_vertex_b); + // Retuns the edge point bu that ID, or the edge with -1 vertices if the + // id is not valid. + static Edge get_edge(const std::vector<Edge> &p_map, int p_id); + +private: + // Read directly from the FBX file. + std::vector<Vector3> m_vertices; + std::vector<Edge> edge_map; + std::vector<int> m_face_indices; + std::vector<int> m_edges; + MappingData<Vector3> m_normals; + MappingData<Vector2> m_uv_0; // first uv coordinates + MappingData<Vector2> m_uv_1; // second uv coordinates + MappingData<Color> m_colors; // colors for the mesh + MappingData<int> m_material_allocation_ids; // slot of material used + + template <class T> + MappingData<T> resolve_vertex_data_array( + const ScopePtr source, + const std::string &MappingInformationType, + const std::string &ReferenceInformationType, + const std::string &dataElementName, + const std::string &indexOverride = ""); +}; + +/* + * DOM class for FBX geometry of type "Shape" + */ +class ShapeGeometry : public Geometry { +public: + /** The class constructor */ + ShapeGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); + + /** The class destructor */ + virtual ~ShapeGeometry(); + + /** Get a list of all vertex points, non-unique*/ + const std::vector<Vector3> &GetVertices() const; + + /** Get a list of all vertex normals or an empty array if + * no normals are specified. */ + const std::vector<Vector3> &GetNormals() const; + + /** Return list of vertex indices. */ + const std::vector<unsigned int> &GetIndices() const; + +private: + std::vector<Vector3> m_vertices; + std::vector<Vector3> m_normals; + std::vector<unsigned int> m_indices; +}; +/** +* DOM class for FBX geometry of type "Line" +*/ +class LineGeometry : public Geometry { +public: + /** The class constructor */ + LineGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); + + /** The class destructor */ + virtual ~LineGeometry(); + + /** Get a list of all vertex points, non-unique*/ + const std::vector<Vector3> &GetVertices() const; + + /** Return list of vertex indices. */ + const std::vector<int> &GetIndices() const; + +private: + std::vector<Vector3> m_vertices; + std::vector<int> m_indices; +}; +} // namespace FBXDocParser + +#endif // FBX_MESH_GEOMETRY_H diff --git a/modules/fbx/fbx_parser/FBXModel.cpp b/modules/fbx/fbx_parser/FBXModel.cpp new file mode 100644 index 0000000000..5867f59d6a --- /dev/null +++ b/modules/fbx/fbx_parser/FBXModel.cpp @@ -0,0 +1,176 @@ +/*************************************************************************/ +/* FBXModel.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXModel.cpp + * @brief Assimp::FBX::Model implementation + */ + +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXMeshGeometry.h" +#include "FBXParser.h" + +namespace FBXDocParser { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Model::Model(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Object(id, element, name), shading("Y") { + const ScopePtr sc = GetRequiredScope(element); + const ElementPtr Shading = sc->GetElement("Shading"); + const ElementPtr Culling = sc->GetElement("Culling"); + + if (Shading) { + shading = GetRequiredToken(Shading, 0)->StringContents(); + } + + if (Culling) { + culling = ParseTokenAsString(GetRequiredToken(Culling, 0)); + } + + props = GetPropertyTable(doc, "Model.FbxNode", element, sc); + ResolveLinks(element, doc); +} + +// ------------------------------------------------------------------------------------------------ +Model::~Model() { + if (props != nullptr) { + delete props; + props = nullptr; + } +} + +ModelLimbNode::ModelLimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Model(id, element, doc, name){}; + +ModelLimbNode::~ModelLimbNode() { +} + +// ------------------------------------------------------------------------------------------------ +void Model::ResolveLinks(const ElementPtr element, const Document &doc) { + const char *const arr[] = { "Geometry", "Material", "NodeAttribute" }; + + // resolve material + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), arr, 3); + + materials.reserve(conns.size()); + geometry.reserve(conns.size()); + attributes.reserve(conns.size()); + for (const Connection *con : conns) { + // material and geometry links should be Object-Object connections + if (con->PropertyName().length()) { + continue; + } + + const Object *const ob = con->SourceObject(); + if (!ob) { + //DOMWarning("failed to read source object for incoming Model link, ignoring",&element); + continue; + } + + const Material *const mat = dynamic_cast<const Material *>(ob); + if (mat) { + materials.push_back(mat); + continue; + } + + const Geometry *const geo = dynamic_cast<const Geometry *>(ob); + if (geo) { + geometry.push_back(geo); + continue; + } + + const NodeAttribute *const att = dynamic_cast<const NodeAttribute *>(ob); + if (att) { + attributes.push_back(att); + continue; + } + + DOMWarning("source object for model link is neither Material, NodeAttribute nor Geometry, ignoring", element); + continue; + } +} + +// ------------------------------------------------------------------------------------------------ +bool Model::IsNull() const { + const std::vector<const NodeAttribute *> &attrs = GetAttributes(); + for (const NodeAttribute *att : attrs) { + const Null *null_tag = dynamic_cast<const Null *>(att); + if (null_tag) { + return true; + } + } + + return false; +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXNodeAttribute.cpp b/modules/fbx/fbx_parser/FBXNodeAttribute.cpp new file mode 100644 index 0000000000..c5051bd147 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXNodeAttribute.cpp @@ -0,0 +1,183 @@ +/*************************************************************************/ +/* FBXNodeAttribute.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXNoteAttribute.cpp + * @brief Assimp::FBX::NodeAttribute (and subclasses) implementation + */ + +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXParser.h" +#include <iostream> + +namespace FBXDocParser { +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +NodeAttribute::NodeAttribute(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Object(id, element, name), props() { + const ScopePtr sc = GetRequiredScope(element); + + const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2)); + + // hack on the deriving type but Null/LimbNode attributes are the only case in which + // the property table is by design absent and no warning should be generated + // for it. + const bool is_null_or_limb = !strcmp(classname.c_str(), "Null") || !strcmp(classname.c_str(), "LimbNode"); + props = GetPropertyTable(doc, "NodeAttribute.Fbx" + classname, element, sc, is_null_or_limb); +} + +// ------------------------------------------------------------------------------------------------ +NodeAttribute::~NodeAttribute() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +CameraSwitcher::CameraSwitcher(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + NodeAttribute(id, element, doc, name) { + const ScopePtr sc = GetRequiredScope(element); + const ElementPtr CameraId = sc->GetElement("CameraId"); + const ElementPtr CameraName = sc->GetElement("CameraName"); + const ElementPtr CameraIndexName = sc->GetElement("CameraIndexName"); + + if (CameraId) { + cameraId = ParseTokenAsInt(GetRequiredToken(CameraId, 0)); + } + + if (CameraName) { + cameraName = GetRequiredToken(CameraName, 0)->StringContents(); + } + + if (CameraIndexName && CameraIndexName->Tokens().size()) { + cameraIndexName = GetRequiredToken(CameraIndexName, 0)->StringContents(); + } +} + +// ------------------------------------------------------------------------------------------------ +CameraSwitcher::~CameraSwitcher() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +Camera::Camera(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + NodeAttribute(id, element, doc, name) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +Camera::~Camera() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +Light::Light(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + NodeAttribute(id, element, doc, name) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +Light::~Light() { +} + +// ------------------------------------------------------------------------------------------------ +Null::Null(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + NodeAttribute(id, element, doc, name) { +} + +// ------------------------------------------------------------------------------------------------ +Null::~Null() { +} + +// ------------------------------------------------------------------------------------------------ +LimbNode::LimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + NodeAttribute(id, element, doc, name) { + //std::cout << "limb node: " << name << std::endl; + //const Scope &sc = GetRequiredScope(element); + + //const ElementPtr const TypeFlag = sc["TypeFlags"]; + + // keep this it can dump new properties for you + // for( auto element : sc.Elements()) + // { + // std::cout << "limbnode element: " << element.first << std::endl; + // } + + // if(TypeFlag) + // { + // // std::cout << "type flag: " << GetRequiredToken(*TypeFlag, 0).StringContents() << std::endl; + // } +} + +// ------------------------------------------------------------------------------------------------ +LimbNode::~LimbNode() { +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXParseTools.h b/modules/fbx/fbx_parser/FBXParseTools.h new file mode 100644 index 0000000000..edc52009b3 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXParseTools.h @@ -0,0 +1,111 @@ +/*************************************************************************/ +/* FBXParseTools.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 FBX_PARSE_TOOLS_H +#define FBX_PARSE_TOOLS_H + +#include "core/error/error_macros.h" +#include "core/string/ustring.h" + +#include <stdint.h> +#include <algorithm> +#include <locale> + +template <class char_t> +inline bool IsNewLine(char_t c) { + return c == '\n' || c == '\r'; +} +template <class char_t> +inline bool IsSpace(char_t c) { + return (c == (char_t)' ' || c == (char_t)'\t'); +} + +template <class char_t> +inline bool IsSpaceOrNewLine(char_t c) { + return IsNewLine(c) || IsSpace(c); +} + +template <class char_t> +inline bool IsLineEnd(char_t c) { + return (c == (char_t)'\r' || c == (char_t)'\n' || c == (char_t)'\0' || c == (char_t)'\f'); +} + +// ------------------------------------------------------------------------------------ +// Special version of the function, providing higher accuracy and safety +// It is mainly used by fast_atof to prevent ugly and unwanted integer overflows. +// ------------------------------------------------------------------------------------ +inline uint64_t strtoul10_64(const char *in, bool &errored, const char **out = 0, unsigned int *max_inout = 0) { + unsigned int cur = 0; + uint64_t value = 0; + + errored = *in < '0' || *in > '9'; + ERR_FAIL_COND_V_MSG(errored, 0, "The string cannot be converted parser error"); + + for (;;) { + if (*in < '0' || *in > '9') { + break; + } + + const uint64_t new_value = (value * (uint64_t)10) + ((uint64_t)(*in - '0')); + + // numeric overflow, we rely on you + if (new_value < value) { + //WARN_PRINT( "Converting the string \" " + in + " \" into a value resulted in overflow." ); + return 0; + } + + value = new_value; + + ++in; + ++cur; + + if (max_inout && *max_inout == cur) { + if (out) { /* skip to end */ + while (*in >= '0' && *in <= '9') { + ++in; + } + *out = in; + } + + return value; + } + } + if (out) { + *out = in; + } + + if (max_inout) { + *max_inout = cur; + } + + return value; +} + +#endif // FBX_PARSE_TOOLS_H diff --git a/modules/fbx/fbx_parser/FBXParser.cpp b/modules/fbx/fbx_parser/FBXParser.cpp new file mode 100644 index 0000000000..208358f55e --- /dev/null +++ b/modules/fbx/fbx_parser/FBXParser.cpp @@ -0,0 +1,1295 @@ +/*************************************************************************/ +/* FBXParser.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXParser.cpp + * @brief Implementation of the FBX parser and the rudimentary DOM that we use + */ + +#include "thirdparty/zlib/zlib.h" +#include <stdlib.h> /* strtol */ + +#include "ByteSwapper.h" +#include "FBXParseTools.h" +#include "FBXParser.h" +#include "FBXTokenizer.h" +#include "core/math/math_defs.h" +#include "core/math/transform.h" +#include "core/math/vector3.h" +#include "core/string/print_string.h" + +using namespace FBXDocParser; +namespace { + +// Initially, we did reinterpret_cast, breaking strict aliasing rules. +// This actually caused trouble on Android, so let's be safe this time. +// https://github.com/assimp/assimp/issues/24 +template <typename T> +T SafeParse(const char *data, const char *end) { + // Actual size validation happens during Tokenization so + // this is valid as an assertion. + (void)(end); + //ai_assert(static_cast<size_t>(end - data) >= sizeof(T)); + T result = static_cast<T>(0); + ::memcpy(&result, data, sizeof(T)); + return result; +} +} // namespace + +namespace FBXDocParser { + +// ------------------------------------------------------------------------------------------------ +Element::Element(const TokenPtr key_token, Parser &parser) : + key_token(key_token) { + TokenPtr n = nullptr; + do { + n = parser.AdvanceToNextToken(); + if (n == nullptr) { + continue; + } + + if (!n) { + print_error("unexpected end of file, expected closing bracket" + String(parser.LastToken()->StringContents().c_str())); + } + + if (n && n->Type() == TokenType_DATA) { + tokens.push_back(n); + TokenPtr prev = n; + n = parser.AdvanceToNextToken(); + + if (n == nullptr) { + break; + } + + if (!n) { + print_error("unexpected end of file, expected bracket, comma or key" + String(parser.LastToken()->StringContents().c_str())); + } + + const TokenType ty = n->Type(); + + // some exporters are missing a comma on the next line + if (ty == TokenType_DATA && prev->Type() == TokenType_DATA && (n->Line() == prev->Line() + 1)) { + tokens.push_back(n); + continue; + } + + if (ty != TokenType_OPEN_BRACKET && ty != TokenType_CLOSE_BRACKET && ty != TokenType_COMMA && ty != TokenType_KEY) { + print_error("unexpected token; expected bracket, comma or key" + String(n->StringContents().c_str())); + } + } + + if (n && n->Type() == TokenType_OPEN_BRACKET) { + compound = new_Scope(parser); + parser.scopes.push_back(compound); + + // current token should be a TOK_CLOSE_BRACKET + n = parser.CurrentToken(); + + if (n && n->Type() != TokenType_CLOSE_BRACKET) { + print_error("expected closing bracket" + String(n->StringContents().c_str())); + } + + parser.AdvanceToNextToken(); + return; + } + } while (n && n->Type() != TokenType_KEY && n->Type() != TokenType_CLOSE_BRACKET); +} + +// ------------------------------------------------------------------------------------------------ +Element::~Element() { +} + +// ------------------------------------------------------------------------------------------------ +Scope::Scope(Parser &parser, bool topLevel) { + if (!topLevel) { + TokenPtr t = parser.CurrentToken(); + if (t->Type() != TokenType_OPEN_BRACKET) { + print_error("expected open bracket" + String(t->StringContents().c_str())); + } + } + + TokenPtr n = parser.AdvanceToNextToken(); + if (n == nullptr) { + print_error("unexpected end of file"); + } + + // note: empty scopes are allowed + while (n && n->Type() != TokenType_CLOSE_BRACKET) { + if (n->Type() != TokenType_KEY) { + print_error("unexpected token, expected TOK_KEY" + String(n->StringContents().c_str())); + } + + const std::string str = n->StringContents(); + + // std::multimap<std::string, ElementPtr> (key and value) + elements.insert(ElementMap::value_type(str, new_Element(n, parser))); + + // Element() should stop at the next Key token (or right after a Close token) + n = parser.CurrentToken(); + if (n == nullptr) { + if (topLevel) { + return; + } + + //print_error("unexpected end of file" + String(parser.LastToken()->StringContents().c_str())); + } + } +} + +// ------------------------------------------------------------------------------------------------ +Scope::~Scope() { + for (ElementMap::value_type &v : elements) { + delete v.second; + v.second = nullptr; + } + + elements.clear(); +} + +// ------------------------------------------------------------------------------------------------ +Parser::Parser(const TokenList &tokens, bool is_binary) : + tokens(tokens), last(), current(), cursor(tokens.begin()), is_binary(is_binary) { + root = new_Scope(*this, true); + scopes.push_back(root); +} + +// ------------------------------------------------------------------------------------------------ +Parser::~Parser() { + for (ScopePtr scope : scopes) { + delete scope; + scope = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::AdvanceToNextToken() { + last = current; + if (cursor == tokens.end()) { + current = nullptr; + } else { + current = *cursor++; + } + return current; +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::CurrentToken() const { + return current; +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::LastToken() const { + return last; +} + +// ------------------------------------------------------------------------------------------------ +uint64_t ParseTokenAsID(const TokenPtr t, const char *&err_out) { + ERR_FAIL_COND_V_MSG(t == nullptr, 0L, "Invalid token passed to ParseTokenAsID"); + err_out = nullptr; + + if (t->Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0L; + } + + if (t->IsBinary()) { + const char *data = t->begin(); + if (data[0] != 'L') { + err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)"; + return 0L; + } + + uint64_t id = SafeParse<uint64_t>(data + 1, t->end()); + return id; + } + + // XXX: should use size_t here + unsigned int length = static_cast<unsigned int>(t->end() - t->begin()); + //ai_assert(length > 0); + + const char *out = nullptr; + bool errored = false; + + const uint64_t id = strtoul10_64(t->begin(), errored, &out, &length); + if (errored || out > t->end()) { + err_out = "failed to parse ID (text)"; + return 0L; + } + + return id; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsID() with print_error handling +uint64_t ParseTokenAsID(const TokenPtr t) { + const char *err = nullptr; + const uint64_t i = ParseTokenAsID(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +size_t ParseTokenAsDim(const TokenPtr t, const char *&err_out) { + // same as ID parsing, except there is a trailing asterisk + err_out = nullptr; + + if (t->Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0; + } + + if (t->IsBinary()) { + const char *data = t->begin(); + if (data[0] != 'L') { + err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)"; + return 0; + } + + uint64_t id = SafeParse<uint64_t>(data + 1, t->end()); + AI_SWAP8(id); + return static_cast<size_t>(id); + } + + if (*t->begin() != '*') { + err_out = "expected asterisk before array dimension"; + return 0; + } + + // XXX: should use size_t here + unsigned int length = static_cast<unsigned int>(t->end() - t->begin()); + if (length == 0) { + err_out = "expected valid integer number after asterisk"; + return 0; + } + + const char *out = nullptr; + bool errored = false; + const size_t id = static_cast<size_t>(strtoul10_64(t->begin() + 1, errored, &out, &length)); + if (errored || out > t->end()) { + print_error("failed to parse id"); + err_out = "failed to parse ID"; + return 0; + } + + return id; +} + +// ------------------------------------------------------------------------------------------------ +float ParseTokenAsFloat(const TokenPtr t, const char *&err_out) { + err_out = nullptr; + + if (t->Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0.0f; + } + + if (t->IsBinary()) { + const char *data = t->begin(); + if (data[0] != 'F' && data[0] != 'D') { + err_out = "failed to parse F(loat) or D(ouble), unexpected data type (binary)"; + return 0.0f; + } + + if (data[0] == 'F') { + return SafeParse<float>(data + 1, t->end()); + } else { + return static_cast<float>(SafeParse<double>(data + 1, t->end())); + } + } + +// need to copy the input string to a temporary buffer +// first - next in the fbx token stream comes ',', +// which fast_atof could interpret as decimal point. +#define MAX_FLOAT_LENGTH 31 + char temp[MAX_FLOAT_LENGTH + 1]; + const size_t length = static_cast<size_t>(t->end() - t->begin()); + std::copy(t->begin(), t->end(), temp); + temp[std::min(static_cast<size_t>(MAX_FLOAT_LENGTH), length)] = '\0'; + + return atof(temp); +} + +// ------------------------------------------------------------------------------------------------ +int ParseTokenAsInt(const TokenPtr t, const char *&err_out) { + err_out = nullptr; + + if (t->Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0; + } + + // binary files are simple to parse + if (t->IsBinary()) { + const char *data = t->begin(); + if (data[0] != 'I') { + err_out = "failed to parse I(nt), unexpected data type (binary)"; + return 0; + } + + int32_t ival = SafeParse<int32_t>(data + 1, t->end()); + AI_SWAP4(ival); + return static_cast<int>(ival); + } + + // ASCII files are unsafe. + const size_t length = static_cast<size_t>(t->end() - t->begin()); + if (length == 0) { + err_out = "expected valid integer number after asterisk"; + ERR_FAIL_V_MSG(0, "expected valid integer number after asterisk"); + } + + // must not be null for strtol to work + char *out = (char *)t->end(); + // string begin, end ptr ref, base 10 + const int value = strtol(t->begin(), &out, 10); + if (out == nullptr || out != t->end()) { + err_out = "failed to parse ID"; + ERR_FAIL_V_MSG(0, "failed to parse ID"); + } + + return value; +} + +// ------------------------------------------------------------------------------------------------ +int64_t ParseTokenAsInt64(const TokenPtr t, const char *&err_out) { + err_out = nullptr; + + if (t->Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0L; + } + + if (t->IsBinary()) { + const char *data = t->begin(); + if (data[0] != 'L') { + err_out = "failed to parse Int64, unexpected data type"; + return 0L; + } + + int64_t id = SafeParse<int64_t>(data + 1, t->end()); + AI_SWAP8(id); + return id; + } + + // XXX: should use size_t here + unsigned int length = static_cast<unsigned int>(t->end() - t->begin()); + //ai_assert(length > 0); + + char *out = nullptr; + const int64_t id = strtol(t->begin(), &out, length); + if (out > t->end()) { + err_out = "failed to parse Int64 (text)"; + return 0L; + } + + return id; +} + +// ------------------------------------------------------------------------------------------------ +std::string ParseTokenAsString(const TokenPtr t, const char *&err_out) { + err_out = nullptr; + + if (t->Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return ""; + } + + if (t->IsBinary()) { + const char *data = t->begin(); + if (data[0] != 'S') { + err_out = "failed to parse String, unexpected data type (binary)"; + return ""; + } + + // read string length + int32_t len = SafeParse<int32_t>(data + 1, t->end()); + AI_SWAP4(len); + + //ai_assert(t.end() - data == 5 + len); + return std::string(data + 5, len); + } + + const size_t length = static_cast<size_t>(t->end() - t->begin()); + if (length < 2) { + err_out = "token is too short to hold a string"; + return ""; + } + + const char *s = t->begin(), *e = t->end() - 1; + if (*s != '\"' || *e != '\"') { + err_out = "expected double quoted string"; + return ""; + } + + return std::string(s + 1, length - 2); +} + +namespace { + +// ------------------------------------------------------------------------------------------------ +// read the type code and element count of a binary data array and stop there +void ReadBinaryDataArrayHead(const char *&data, const char *end, char &type, uint32_t &count, + const ElementPtr el) { + TokenPtr token = el->KeyToken(); + if (static_cast<size_t>(end - data) < 5) { + print_error("binary data array is too short, need five (5) bytes for type signature and element count: " + String(token->StringContents().c_str())); + } + + // data type + type = *data; + + // read number of elements + uint32_t len = SafeParse<uint32_t>(data + 1, end); + AI_SWAP4(len); + + count = len; + data += 5; +} + +// ------------------------------------------------------------------------------------------------ +// read binary data array, assume cursor points to the 'compression mode' field (i.e. behind the header) +void ReadBinaryDataArray(char type, uint32_t count, const char *&data, const char *end, + std::vector<char> &buff, + const ElementPtr /*el*/) { + uint32_t encmode = SafeParse<uint32_t>(data, end); + AI_SWAP4(encmode); + data += 4; + + // next comes the compressed length + uint32_t comp_len = SafeParse<uint32_t>(data, end); + AI_SWAP4(comp_len); + data += 4; + + //ai_assert(data + comp_len == end); + + // determine the length of the uncompressed data by looking at the type signature + uint32_t stride = 0; + switch (type) { + case 'f': + case 'i': + stride = 4; + break; + + case 'd': + case 'l': + stride = 8; + break; + } + + const uint32_t full_length = stride * count; + buff.resize(full_length); + + if (encmode == 0) { + //ai_assert(full_length == comp_len); + + // plain data, no compression + std::copy(data, end, buff.begin()); + } else if (encmode == 1) { + // zlib/deflate, next comes ZIP head (0x78 0x01) + // see http://www.ietf.org/rfc/rfc1950.txt + + z_stream zstream; + zstream.opaque = Z_NULL; + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.data_type = Z_BINARY; + + // http://hewgill.com/journal/entries/349-how-to-decompress-gzip-stream-with-zlib + if (Z_OK != inflateInit(&zstream)) { + print_error("failure initializing zlib"); + } + + zstream.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(data)); + zstream.avail_in = comp_len; + + zstream.avail_out = static_cast<uInt>(buff.size()); + zstream.next_out = reinterpret_cast<Bytef *>(&*buff.begin()); + const int ret = inflate(&zstream, Z_FINISH); + + if (ret != Z_STREAM_END && ret != Z_OK) { + print_error("failure decompressing compressed data section"); + } + + // terminate zlib + inflateEnd(&zstream); + } +#ifdef ASSIMP_BUILD_DEBUG + else { + // runtime check for this happens at tokenization stage + //ai_assert(false); + } +#endif + + data += comp_len; + //ai_assert(data == end); +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +// read an array of float3 tuples +void ParseVectorDataArray(std::vector<Vector3> &out, const ElementPtr el) { + out.resize(0); + + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + if (tok.empty()) { + print_error("unexpected empty element" + String(token->StringContents().c_str())); + } + + if (tok[0]->IsBinary()) { + const char *data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (count % 3 != 0) { + print_error("number of floats is not a multiple of three (3) (binary)" + String(token->StringContents().c_str())); + } + + if (!count) { + return; + } + + if (type != 'd' && type != 'f') { + print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + //ai_assert(data == end); + //ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); + + const uint32_t count3 = count / 3; + out.reserve(count3); + + if (type == 'd') { + const double *d = reinterpret_cast<const double *>(&buff[0]); + for (unsigned int i = 0; i < count3; ++i, d += 3) { + out.push_back(Vector3(static_cast<real_t>(d[0]), + static_cast<real_t>(d[1]), + static_cast<real_t>(d[2]))); + } + // for debugging + /*for ( size_t i = 0; i < out.size(); i++ ) { + aiVector3D vec3( out[ i ] ); + std::stringstream stream; + stream << " vec3.x = " << vec3.x << " vec3.y = " << vec3.y << " vec3.z = " << vec3.z << std::endl; + DefaultLogger::get()->info( stream.str() ); + }*/ + } else if (type == 'f') { + const float *f = reinterpret_cast<const float *>(&buff[0]); + for (unsigned int i = 0; i < count3; ++i, f += 3) { + out.push_back(Vector3(f[0], f[1], f[2])); + } + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // may throw bad_alloc if the input is rubbish, but this need + // not to be prevented - importing would fail but we wouldn't + // crash since assimp handles this case properly. + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + if (a->Tokens().size() % 3 != 0) { + print_error("number of floats is not a multiple of three (3)" + String(token->StringContents().c_str())); + } else { + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + Vector3 v; + v.x = ParseTokenAsFloat(*it++); + v.y = ParseTokenAsFloat(*it++); + v.z = ParseTokenAsFloat(*it++); + + out.push_back(v); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of color4 tuples +void ParseVectorDataArray(std::vector<Color> &out, const ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + + TokenPtr token = el->KeyToken(); + + if (tok.empty()) { + print_error("unexpected empty element" + String(token->StringContents().c_str())); + } + + if (tok[0]->IsBinary()) { + const char *data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (count % 4 != 0) { + print_error("number of floats is not a multiple of four (4) (binary)" + String(token->StringContents().c_str())); + } + + if (!count) { + return; + } + + if (type != 'd' && type != 'f') { + print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + //ai_assert(data == end); + //ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); + + const uint32_t count4 = count / 4; + out.reserve(count4); + + if (type == 'd') { + const double *d = reinterpret_cast<const double *>(&buff[0]); + for (unsigned int i = 0; i < count4; ++i, d += 4) { + out.push_back(Color(static_cast<float>(d[0]), + static_cast<float>(d[1]), + static_cast<float>(d[2]), + static_cast<float>(d[3]))); + } + } else if (type == 'f') { + const float *f = reinterpret_cast<const float *>(&buff[0]); + for (unsigned int i = 0; i < count4; ++i, f += 4) { + out.push_back(Color(f[0], f[1], f[2], f[3])); + } + } + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() above + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + if (a->Tokens().size() % 4 != 0) { + print_error("number of floats is not a multiple of four (4)" + String(token->StringContents().c_str())); + } + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + Color v; + v.r = ParseTokenAsFloat(*it++); + v.g = ParseTokenAsFloat(*it++); + v.b = ParseTokenAsFloat(*it++); + v.a = ParseTokenAsFloat(*it++); + + out.push_back(v); + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of float2 tuples +void ParseVectorDataArray(std::vector<Vector2> &out, const ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + if (tok.empty()) { + print_error("unexpected empty element" + String(token->StringContents().c_str())); + } + + if (tok[0]->IsBinary()) { + const char *data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (count % 2 != 0) { + print_error("number of floats is not a multiple of two (2) (binary)" + String(token->StringContents().c_str())); + } + + if (!count) { + return; + } + + if (type != 'd' && type != 'f') { + print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + //ai_assert(data == end); + //ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); + + const uint32_t count2 = count / 2; + out.reserve(count2); + + if (type == 'd') { + const double *d = reinterpret_cast<const double *>(&buff[0]); + for (unsigned int i = 0; i < count2; ++i, d += 2) { + out.push_back(Vector2(static_cast<float>(d[0]), + static_cast<float>(d[1]))); + } + } else if (type == 'f') { + const float *f = reinterpret_cast<const float *>(&buff[0]); + for (unsigned int i = 0; i < count2; ++i, f += 2) { + out.push_back(Vector2(f[0], f[1])); + } + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() above + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + if (a->Tokens().size() % 2 != 0) { + print_error("number of floats is not a multiple of two (2)" + String(token->StringContents().c_str())); + } else { + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + Vector2 v; + v.x = ParseTokenAsFloat(*it++); + v.y = ParseTokenAsFloat(*it++); + out.push_back(v); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of ints +void ParseVectorDataArray(std::vector<int> &out, const ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + if (tok.empty()) { + print_error("unexpected empty element" + String(token->StringContents().c_str())); + } + + if (tok[0]->IsBinary()) { + const char *data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (!count) { + return; + } + + if (type != 'i') { + print_error("expected int array (binary)" + String(token->StringContents().c_str())); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + //ai_assert(data == end); + //ai_assert(buff.size() == count * 4); + + out.reserve(count); + + const int32_t *ip = reinterpret_cast<const int32_t *>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++ip) { + int32_t val = *ip; + AI_SWAP4(val); + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + const int ival = ParseTokenAsInt(*it++); + out.push_back(ival); + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of floats +void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + if (tok.empty()) { + print_error("unexpected empty element: " + String(token->StringContents().c_str())); + } + + if (tok[0]->IsBinary()) { + const char *data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (!count) { + return; + } + + if (type != 'd' && type != 'f') { + print_error("expected float or double array (binary) " + String(token->StringContents().c_str())); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + //ai_assert(data == end); + //ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); + + if (type == 'd') { + const double *d = reinterpret_cast<const double *>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++d) { + out.push_back(static_cast<float>(*d)); + } + } else if (type == 'f') { + const float *f = reinterpret_cast<const float *>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++f) { + out.push_back(*f); + } + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + const float ival = ParseTokenAsFloat(*it++); + out.push_back(ival); + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of uints +void ParseVectorDataArray(std::vector<unsigned int> &out, const ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + const TokenPtr token = el->KeyToken(); + + ERR_FAIL_COND_MSG(!token, "invalid ParseVectorDataArrat token invalid"); + + if (tok.empty()) { + print_error("unexpected empty element: " + String(token->StringContents().c_str())); + } + + if (tok[0]->IsBinary()) { + const char *data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (!count) { + return; + } + + if (type != 'i') { + print_error("expected (u)int array (binary)" + String(token->StringContents().c_str())); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + //ai_assert(data == end); + //ai_assert(buff.size() == count * 4); + + out.reserve(count); + + const int32_t *ip = reinterpret_cast<const int32_t *>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++ip) { + int32_t val = *ip; + if (val < 0) { + print_error("encountered negative integer index (binary)"); + } + + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + const int ival = ParseTokenAsInt(*it++); + if (ival < 0) { + print_error("encountered negative integer index"); + } + out.push_back(static_cast<unsigned int>(ival)); + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of uint64_ts +void ParseVectorDataArray(std::vector<uint64_t> &out, const ElementPtr el) { + out.resize(0); + + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + ERR_FAIL_COND(!token); + + if (tok.empty()) { + print_error("unexpected empty element " + String(token->StringContents().c_str())); + } + + if (tok[0]->IsBinary()) { + const char *data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (!count) { + return; + } + + if (type != 'l') { + print_error("expected long array (binary): " + String(token->StringContents().c_str())); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + //ai_assert(data == end); + //ai_assert(buff.size() == count * 8); + + out.reserve(count); + + const uint64_t *ip = reinterpret_cast<const uint64_t *>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++ip) { + uint64_t val = *ip; + AI_SWAP8(val); + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + const uint64_t ival = ParseTokenAsID(*it++); + + out.push_back(ival); + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of int64_ts +void ParseVectorDataArray(std::vector<int64_t> &out, const ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + ERR_FAIL_COND(!token); + if (tok.empty()) { + print_error("unexpected empty element: " + String(token->StringContents().c_str())); + } + + if (tok[0]->IsBinary()) { + const char *data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (!count) { + return; + } + + if (type != 'l') { + print_error("expected long array (binary) " + String(token->StringContents().c_str())); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + //ai_assert(data == end); + //ai_assert(buff.size() == count * 8); + + out.reserve(count); + + const int64_t *ip = reinterpret_cast<const int64_t *>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++ip) { + int64_t val = *ip; + AI_SWAP8(val); + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + const int64_t val = ParseTokenAsInt64(*it++); + out.push_back(val); + } +} + +// ------------------------------------------------------------------------------------------------ +Transform ReadMatrix(const ElementPtr element) { + std::vector<float> values; + ParseVectorDataArray(values, element); + + if (values.size() != 16) { + print_error("expected 16 matrix elements"); + } + + // clean values to prevent any IBM damage on inverse() / affine_inverse() + for (float &value : values) { + if (::Math::is_equal_approx(0, value)) { + value = 0; + } + } + + Transform xform; + Basis basis; + + basis.set( + Vector3(values[0], values[1], values[2]), + Vector3(values[4], values[5], values[6]), + Vector3(values[8], values[9], values[10])); + + xform.basis = basis; + xform.origin = Vector3(values[12], values[13], values[14]); + // determine if we need to think about this with dynamic rotation order? + // for example: + // xform.basis = z_axis * y_axis * x_axis; + //xform.basis.transpose(); + + print_verbose("xform verbose basis: " + (xform.basis.get_euler() * (180 / Math_PI)) + " xform origin:" + xform.origin); + + return xform; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsString() with print_error handling +std::string ParseTokenAsString(const TokenPtr t) { + ERR_FAIL_COND_V(!t, ""); + const char *err; + const std::string &i = ParseTokenAsString(t, err); + if (err) { + print_error(String(err) + ", " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// extract a required element from a scope, abort if the element cannot be found +ElementPtr GetRequiredElement(const ScopePtr sc, const std::string &index, const ElementPtr element /*= NULL*/) { + const ElementPtr el = sc->GetElement(index); + TokenPtr token = el->KeyToken(); + ERR_FAIL_COND_V(!token, nullptr); + if (!el) { + print_error("did not find required element \"" + String(index.c_str()) + "\" " + String(token->StringContents().c_str())); + } + return el; +} + +bool HasElement(const ScopePtr sc, const std::string &index) { + const ElementPtr el = sc->GetElement(index); + if (nullptr == el) { + return false; + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ +// extract a required element from a scope, abort if the element cannot be found +ElementPtr GetOptionalElement(const ScopePtr sc, const std::string &index, const ElementPtr element /*= NULL*/) { + const ElementPtr el = sc->GetElement(index); + return el; +} + +// ------------------------------------------------------------------------------------------------ +// extract required compound scope +ScopePtr GetRequiredScope(const ElementPtr el) { + if (el) { + ScopePtr s = el->Compound(); + TokenPtr token = el->KeyToken(); + ERR_FAIL_COND_V(!token, nullptr); + if (s) { + return s; + } + + ERR_FAIL_V_MSG(nullptr, "expected compound scope " + String(token->StringContents().c_str())); + } + + ERR_FAIL_V_MSG(nullptr, "Invalid element supplied to parser"); +} + +// ------------------------------------------------------------------------------------------------ +// get token at a particular index +TokenPtr GetRequiredToken(const ElementPtr el, unsigned int index) { + if (el) { + const TokenList &x = el->Tokens(); + TokenPtr token = el->KeyToken(); + + ERR_FAIL_COND_V(!token, nullptr); + + if (index >= x.size()) { + ERR_FAIL_V_MSG(nullptr, "missing token at index: " + itos(index) + " " + String(token->StringContents().c_str())); + } + + return x[index]; + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsDim() with print_error handling +size_t ParseTokenAsDim(const TokenPtr t) { + const char *err; + const size_t i = ParseTokenAsDim(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsFloat() with print_error handling +float ParseTokenAsFloat(const TokenPtr t) { + const char *err; + const float i = ParseTokenAsFloat(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsInt() with print_error handling +int ParseTokenAsInt(const TokenPtr t) { + const char *err; + const int i = ParseTokenAsInt(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsInt64() with print_error handling +int64_t ParseTokenAsInt64(const TokenPtr t) { + const char *err; + const int64_t i = ParseTokenAsInt64(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXParser.h b/modules/fbx/fbx_parser/FBXParser.h new file mode 100644 index 0000000000..3cba81ff83 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXParser.h @@ -0,0 +1,263 @@ +/*************************************************************************/ +/* FBXParser.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXParser.h + * @brief FBX parsing code + */ +#ifndef FBX_PARSER_H +#define FBX_PARSER_H + +#include <stdint.h> +#include <map> +#include <memory> + +#include "core/math/color.h" +#include "core/math/transform.h" +#include "core/math/vector2.h" +#include "core/math/vector3.h" + +#include "FBXTokenizer.h" + +namespace FBXDocParser { + +class Scope; +class Parser; +class Element; + +typedef Element *ElementPtr; +typedef Scope *ScopePtr; + +typedef std::vector<ScopePtr> ScopeList; +typedef std::multimap<std::string, ElementPtr> ElementMap; +typedef std::pair<ElementMap::const_iterator, ElementMap::const_iterator> ElementCollection; + +#define new_Scope new Scope +#define new_Element new Element + +/** FBX data entity that consists of a key:value tuple. + * + * Example: + * @verbatim + * AnimationCurve: 23, "AnimCurve::", "" { + * [..] + * } + * @endverbatim + * + * As can be seen in this sample, elements can contain nested #Scope + * as their trailing member. **/ +class Element { +public: + Element(TokenPtr key_token, Parser &parser); + ~Element(); + + ScopePtr Compound() const { + return compound; + } + + TokenPtr KeyToken() const { + return key_token; + } + + const TokenList &Tokens() const { + return tokens; + } + +private: + TokenList tokens; + ScopePtr compound = nullptr; + std::vector<ScopePtr> compound_scope; + TokenPtr key_token = nullptr; +}; + +/** FBX data entity that consists of a 'scope', a collection + * of not necessarily unique #Element instances. + * + * Example: + * @verbatim + * GlobalSettings: { + * Version: 1000 + * Properties70: + * [...] + * } + * @endverbatim */ +class Scope { +public: + Scope(Parser &parser, bool topLevel = false); + ~Scope(); + + ElementPtr GetElement(const std::string &index) const { + ElementMap::const_iterator it = elements.find(index); + return it == elements.end() ? nullptr : (*it).second; + } + + ElementPtr FindElementCaseInsensitive(const std::string &elementName) const { + for (auto element = elements.begin(); element != elements.end(); ++element) { + if (element->first.compare(elementName)) { + return element->second; + } + } + + // nothing to reference / expired. + return nullptr; + } + + ElementCollection GetCollection(const std::string &index) const { + return elements.equal_range(index); + } + + const ElementMap &Elements() const { + return elements; + } + +private: + ElementMap elements; +}; + +/** FBX parsing class, takes a list of input tokens and generates a hierarchy + * of nested #Scope instances, representing the fbx DOM.*/ +class Parser { +public: + /** Parse given a token list. Does not take ownership of the tokens - + * the objects must persist during the entire parser lifetime */ + Parser(const TokenList &tokens, bool is_binary); + ~Parser(); + + ScopePtr GetRootScope() const { + return root; + } + + bool IsBinary() const { + return is_binary; + } + +private: + friend class Scope; + friend class Element; + + TokenPtr AdvanceToNextToken(); + TokenPtr LastToken() const; + TokenPtr CurrentToken() const; + +private: + ScopeList scopes; + const TokenList &tokens; + + TokenPtr last = nullptr, current = nullptr; + TokenList::const_iterator cursor; + ScopePtr root = nullptr; + + const bool is_binary; +}; + +/* token parsing - this happens when building the DOM out of the parse-tree*/ +uint64_t ParseTokenAsID(const TokenPtr t, const char *&err_out); +size_t ParseTokenAsDim(const TokenPtr t, const char *&err_out); +float ParseTokenAsFloat(const TokenPtr t, const char *&err_out); +int ParseTokenAsInt(const TokenPtr t, const char *&err_out); +int64_t ParseTokenAsInt64(const TokenPtr t, const char *&err_out); +std::string ParseTokenAsString(const TokenPtr t, const char *&err_out); + +/* wrapper around ParseTokenAsXXX() with DOMError handling */ +uint64_t ParseTokenAsID(const TokenPtr t); +size_t ParseTokenAsDim(const TokenPtr t); +float ParseTokenAsFloat(const TokenPtr t); +int ParseTokenAsInt(const TokenPtr t); +int64_t ParseTokenAsInt64(const TokenPtr t); +std::string ParseTokenAsString(const TokenPtr t); + +/* read data arrays */ +void ParseVectorDataArray(std::vector<Vector3> &out, const ElementPtr el); +void ParseVectorDataArray(std::vector<Color> &out, const ElementPtr el); +void ParseVectorDataArray(std::vector<Vector2> &out, const ElementPtr el); +void ParseVectorDataArray(std::vector<int> &out, const ElementPtr el); +void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el); +void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el); +void ParseVectorDataArray(std::vector<unsigned int> &out, const ElementPtr el); +void ParseVectorDataArray(std::vector<uint64_t> &out, const ElementPtr ep); +void ParseVectorDataArray(std::vector<int64_t> &out, const ElementPtr el); +bool HasElement(const ScopePtr sc, const std::string &index); + +// extract a required element from a scope, abort if the element cannot be found +ElementPtr GetRequiredElement(const ScopePtr sc, const std::string &index, const ElementPtr element = nullptr); +ScopePtr GetRequiredScope(const ElementPtr el); // New in 2020. (less likely to destroy application) +ElementPtr GetOptionalElement(const ScopePtr sc, const std::string &index, const ElementPtr element = nullptr); +// extract required compound scope +ScopePtr GetRequiredScope(const ElementPtr el); +// get token at a particular index +TokenPtr GetRequiredToken(const ElementPtr el, unsigned int index); + +// ------------------------------------------------------------------------------------------------ +// read a 4x4 matrix from an array of 16 floats +Transform ReadMatrix(const ElementPtr element); +} // namespace FBXDocParser + +#endif // FBX_PARSER_H diff --git a/modules/fbx/fbx_parser/FBXPose.cpp b/modules/fbx/fbx_parser/FBXPose.cpp new file mode 100644 index 0000000000..1bee2fa2f0 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXPose.cpp @@ -0,0 +1,104 @@ +/*************************************************************************/ +/* FBXPose.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXNoteAttribute.cpp + * @brief Assimp::FBX::NodeAttribute (and subclasses) implementation + */ + +#include "FBXDocument.h" +#include "FBXParser.h" +#include <iostream> + +namespace FBXDocParser { + +class FbxPoseNode; +// ------------------------------------------------------------------------------------------------ +FbxPose::FbxPose(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : + Object(id, element, name) { + const ScopePtr sc = GetRequiredScope(element); + //const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2)); + + const ElementCollection &PoseNodes = sc->GetCollection("PoseNode"); + for (ElementMap::const_iterator it = PoseNodes.first; it != PoseNodes.second; ++it) { + std::string entry_name = (*it).first; + ElementPtr some_element = (*it).second; + FbxPoseNode *pose_node = new FbxPoseNode(some_element, doc, entry_name); + pose_nodes.push_back(pose_node); + } +} + +// ------------------------------------------------------------------------------------------------ +FbxPose::~FbxPose() { + pose_nodes.clear(); + // empty +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXProperties.cpp b/modules/fbx/fbx_parser/FBXProperties.cpp new file mode 100644 index 0000000000..2994a84dbd --- /dev/null +++ b/modules/fbx/fbx_parser/FBXProperties.cpp @@ -0,0 +1,237 @@ +/*************************************************************************/ +/* FBXProperties.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXProperties.cpp + * @brief Implementation of the FBX dynamic properties system + */ + +#include "FBXProperties.h" +#include "FBXDocumentUtil.h" +#include "FBXParser.h" +#include "FBXTokenizer.h" + +namespace FBXDocParser { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Property::Property() { +} + +// ------------------------------------------------------------------------------------------------ +Property::~Property() { +} + +namespace { + +// ------------------------------------------------------------------------------------------------ +// read a typed property out of a FBX element. The return value is NULL if the property cannot be read. +PropertyPtr ReadTypedProperty(const ElementPtr element) { + //ai_assert(element.KeyToken().StringContents() == "P"); + + const TokenList &tok = element->Tokens(); + //ai_assert(tok.size() >= 5); + + const std::string &s = ParseTokenAsString(tok[1]); + const char *const cs = s.c_str(); + if (!strcmp(cs, "KString")) { + return new TypedProperty<std::string>(ParseTokenAsString(tok[4])); + } else if (!strcmp(cs, "bool") || !strcmp(cs, "Bool")) { + return new TypedProperty<bool>(ParseTokenAsInt(tok[4]) != 0); + } else if (!strcmp(cs, "int") || !strcmp(cs, "Int") || !strcmp(cs, "enum") || !strcmp(cs, "Enum")) { + return new TypedProperty<int>(ParseTokenAsInt(tok[4])); + } else if (!strcmp(cs, "ULongLong")) { + return new TypedProperty<uint64_t>(ParseTokenAsID(tok[4])); + } else if (!strcmp(cs, "KTime")) { + return new TypedProperty<int64_t>(ParseTokenAsInt64(tok[4])); + } else if (!strcmp(cs, "Vector3D") || + !strcmp(cs, "ColorRGB") || + !strcmp(cs, "Vector") || + !strcmp(cs, "Color") || + !strcmp(cs, "Lcl Translation") || + !strcmp(cs, "Lcl Rotation") || + !strcmp(cs, "Lcl Scaling")) { + return new TypedProperty<Vector3>(Vector3( + ParseTokenAsFloat(tok[4]), + ParseTokenAsFloat(tok[5]), + ParseTokenAsFloat(tok[6]))); + } else if (!strcmp(cs, "double") || !strcmp(cs, "Number") || !strcmp(cs, "Float") || !strcmp(cs, "float") || !strcmp(cs, "FieldOfView") || !strcmp(cs, "UnitScaleFactor")) { + return new TypedProperty<float>(ParseTokenAsFloat(tok[4])); + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// peek into an element and check if it contains a FBX property, if so return its name. +std::string PeekPropertyName(const Element &element) { + //ai_assert(element.KeyToken().StringContents() == "P"); + const TokenList &tok = element.Tokens(); + if (tok.size() < 4) { + return ""; + } + + return ParseTokenAsString(tok[0]); +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +PropertyTable::PropertyTable() : + templateProps(), element() { +} + +// ------------------------------------------------------------------------------------------------ +PropertyTable::PropertyTable(const ElementPtr element, const PropertyTable *templateProps) : + templateProps(templateProps), element(element) { + const ScopePtr scope = GetRequiredScope(element); + ERR_FAIL_COND(!scope); + for (const ElementMap::value_type &v : scope->Elements()) { + if (v.first != "P") { + DOMWarning("expected only P elements in property table", v.second); + continue; + } + + const std::string &name = PeekPropertyName(*v.second); + if (!name.length()) { + DOMWarning("could not read property name", v.second); + continue; + } + + LazyPropertyMap::const_iterator it = lazyProps.find(name); + if (it != lazyProps.end()) { + DOMWarning("duplicate property name, will hide previous value: " + name, v.second); + continue; + } + + // since the above checks for duplicates we can be sure to insert the only match here. + lazyProps[name] = v.second; + } +} + +// ------------------------------------------------------------------------------------------------ +PropertyTable::~PropertyTable() { + for (PropertyMap::value_type &v : props) { + delete v.second; + } +} + +// ------------------------------------------------------------------------------------------------ +PropertyPtr PropertyTable::Get(const std::string &name) const { + PropertyMap::const_iterator it = props.find(name); + if (it == props.end()) { + // hasn't been parsed yet? + LazyPropertyMap::const_iterator lit = lazyProps.find(name); + if (lit != lazyProps.end()) { + props[name] = ReadTypedProperty(lit->second); + it = props.find(name); + + //ai_assert(it != props.end()); + } + + if (it == props.end()) { + // check property template + if (templateProps) { + return templateProps->Get(name); + } + + return nullptr; + } + } + + return (*it).second; +} + +DirectPropertyMap PropertyTable::GetUnparsedProperties() const { + DirectPropertyMap result; + + // Loop through all the lazy properties (which is all the properties) + for (const LazyPropertyMap::value_type &element : lazyProps) { + // Skip parsed properties + if (props.end() != props.find(element.first)) + continue; + + // Read the element's value. + // Wrap the naked pointer (since the call site is required to acquire ownership) + // std::unique_ptr from C++11 would be preferred both as a wrapper and a return value. + Property *prop = ReadTypedProperty(element.second); + + // Element could not be read. Skip it. + if (!prop) + continue; + + // Add to result + result[element.first] = prop; + } + + return result; +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXProperties.h b/modules/fbx/fbx_parser/FBXProperties.h new file mode 100644 index 0000000000..a007660c45 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXProperties.h @@ -0,0 +1,221 @@ +/*************************************************************************/ +/* FBXProperties.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXProperties.h + * @brief FBX dynamic properties + */ +#ifndef FBX_PROPERTIES_H +#define FBX_PROPERTIES_H + +#include "FBXParser.h" +#include <map> +#include <memory> +#include <string> +#include <vector> + +namespace FBXDocParser { + +// Forward declarations +class Element; + +/** Represents a dynamic property. Type info added by deriving classes, + * see #TypedProperty. + Example: + @verbatim + P: "ShininessExponent", "double", "Number", "",0.5 + @endvebatim +*/ +class Property { +protected: + Property(); + +public: + virtual ~Property(); + +public: + template <typename T> + const T *As() const { + return dynamic_cast<const T *>(this); + } +}; + +template <typename T> +class TypedProperty : public Property { +public: + explicit TypedProperty(const T &value) : + value(value) { + // empty + } + + const T &Value() const { + return value; + } + +private: + T value; +}; + +#define new_Property new Property +typedef Property *PropertyPtr; +typedef std::map<std::string, PropertyPtr> DirectPropertyMap; +typedef std::map<std::string, PropertyPtr> PropertyMap; +typedef std::map<std::string, ElementPtr> LazyPropertyMap; + +/** + * Represents a property table as can be found in the newer FBX files (Properties60, Properties70) + */ +class PropertyTable { +public: + // in-memory property table with no source element + PropertyTable(); + PropertyTable(const ElementPtr element, const PropertyTable *templateProps); + ~PropertyTable(); + + PropertyPtr Get(const std::string &name) const; + + // PropertyTable's need not be coupled with FBX elements so this can be NULL + ElementPtr GetElement() const { + return element; + } + + PropertyMap &GetProperties() const { + return props; + } + + const LazyPropertyMap &GetLazyProperties() const { + return lazyProps; + } + + const PropertyTable *TemplateProps() const { + return templateProps; + } + + DirectPropertyMap GetUnparsedProperties() const; + +private: + LazyPropertyMap lazyProps; + mutable PropertyMap props; + const PropertyTable *templateProps = nullptr; + const ElementPtr element = nullptr; +}; + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline T PropertyGet(const PropertyTable *in, const std::string &name, const T &defaultValue) { + PropertyPtr prop = in->Get(name); + if (nullptr == prop) { + return defaultValue; + } + + // strong typing, no need to be lenient + const TypedProperty<T> *const tprop = prop->As<TypedProperty<T>>(); + if (nullptr == tprop) { + return defaultValue; + } + + return tprop->Value(); +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline T PropertyGet(const PropertyTable *in, const std::string &name, bool &result, bool useTemplate = false) { + PropertyPtr prop = in->Get(name); + if (nullptr == prop) { + if (!useTemplate) { + result = false; + return T(); + } + const PropertyTable *templ = in->TemplateProps(); + if (nullptr == templ) { + result = false; + return T(); + } + prop = templ->Get(name); + if (nullptr == prop) { + result = false; + return T(); + } + } + + // strong typing, no need to be lenient + const TypedProperty<T> *const tprop = prop->As<TypedProperty<T>>(); + if (nullptr == tprop) { + result = false; + return T(); + } + + result = true; + return tprop->Value(); +} +} // namespace FBXDocParser + +#endif // FBX_PROPERTIES_H diff --git a/modules/fbx/fbx_parser/FBXTokenizer.cpp b/modules/fbx/fbx_parser/FBXTokenizer.cpp new file mode 100644 index 0000000000..3748bfabf8 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXTokenizer.cpp @@ -0,0 +1,251 @@ +/*************************************************************************/ +/* FBXTokenizer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXTokenizer.cpp + * @brief Implementation of the FBX broadphase lexer + */ + +// tab width for logging columns +#define ASSIMP_FBX_TAB_WIDTH 4 + +#include "FBXTokenizer.h" +#include "core/string/print_string.h" + +namespace FBXDocParser { + +// ------------------------------------------------------------------------------------------------ +Token::Token(const char *p_sbegin, const char *p_send, TokenType p_type, unsigned int p_line, unsigned int p_column) : + sbegin(p_sbegin), + send(p_send), + type(p_type), + line(p_line), + column(p_column) { +#ifdef DEBUG_ENABLED + contents = std::string(sbegin, static_cast<size_t>(send - sbegin)); +#endif +} + +// ------------------------------------------------------------------------------------------------ +Token::~Token() { +} + +namespace { + +// ------------------------------------------------------------------------------------------------ +void TokenizeError(const std::string &message, unsigned int line, unsigned int column) { + print_error("[FBX-Tokenize]" + String(message.c_str()) + " " + itos(line) + ":" + itos(column)); +} + +// process a potential data token up to 'cur', adding it to 'output_tokens'. +// ------------------------------------------------------------------------------------------------ +void ProcessDataToken(TokenList &output_tokens, const char *&start, const char *&end, + unsigned int line, + unsigned int column, + TokenType type = TokenType_DATA, + bool must_have_token = false) { + if (start && end) { + // sanity check: + // tokens should have no whitespace outside quoted text and [start,end] should + // properly delimit the valid range. + bool in_double_quotes = false; + for (const char *c = start; c != end + 1; ++c) { + if (*c == '\"') { + in_double_quotes = !in_double_quotes; + } + + if (!in_double_quotes && IsSpaceOrNewLine(*c)) { + TokenizeError("unexpected whitespace in token", line, column); + } + } + + if (in_double_quotes) { + TokenizeError("non-terminated double quotes", line, column); + } + + output_tokens.push_back(new_Token(start, end + 1, type, line, column)); + } else if (must_have_token) { + TokenizeError("unexpected character, expected data token", line, column); + } + + start = end = nullptr; +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +void Tokenize(TokenList &output_tokens, const char *input, size_t length) { + // line and column numbers numbers are one-based + unsigned int line = 1; + unsigned int column = 1; + + bool comment = false; + bool in_double_quotes = false; + bool pending_data_token = false; + + const char *token_begin = nullptr, *token_end = nullptr; + + // input (starting string), *cur the current string, column += + // modified to fix strlen() and stop buffer overflow + for (size_t x = 0; x < length; x++) { + const char c = input[x]; + const char *cur = &input[x]; + column += (c == '\t' ? ASSIMP_FBX_TAB_WIDTH : 1); + + if (IsLineEnd(c)) { + comment = false; + + column = 0; + ++line; + } + + if (comment) { + continue; + } + + if (in_double_quotes) { + if (c == '\"') { + in_double_quotes = false; + token_end = cur; + + ProcessDataToken(output_tokens, token_begin, token_end, line, column); + pending_data_token = false; + } + continue; + } + + switch (c) { + case '\"': + if (token_begin) { + TokenizeError("unexpected double-quote", line, column); + } + token_begin = cur; + in_double_quotes = true; + continue; + + case ';': + ProcessDataToken(output_tokens, token_begin, token_end, line, column); + comment = true; + continue; + + case '{': + ProcessDataToken(output_tokens, token_begin, token_end, line, column); + output_tokens.push_back(new_Token(cur, cur + 1, TokenType_OPEN_BRACKET, line, column)); + continue; + + case '}': + ProcessDataToken(output_tokens, token_begin, token_end, line, column); + output_tokens.push_back(new_Token(cur, cur + 1, TokenType_CLOSE_BRACKET, line, column)); + continue; + + case ',': + if (pending_data_token) { + ProcessDataToken(output_tokens, token_begin, token_end, line, column, TokenType_DATA, true); + } + output_tokens.push_back(new_Token(cur, cur + 1, TokenType_COMMA, line, column)); + continue; + + case ':': + if (pending_data_token) { + ProcessDataToken(output_tokens, token_begin, token_end, line, column, TokenType_KEY, true); + } else { + TokenizeError("unexpected colon", line, column); + } + continue; + } + + if (IsSpaceOrNewLine(c)) { + if (token_begin) { + // peek ahead and check if the next token is a colon in which + // case this counts as KEY token. + TokenType type = TokenType_DATA; + for (const char *peek = cur; *peek && IsSpaceOrNewLine(*peek); ++peek) { + if (*peek == ':') { + type = TokenType_KEY; + cur = peek; + break; + } + } + + ProcessDataToken(output_tokens, token_begin, token_end, line, column, type); + } + + pending_data_token = false; + } else { + token_end = cur; + if (!token_begin) { + token_begin = cur; + } + + pending_data_token = true; + } + } +} +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXTokenizer.h b/modules/fbx/fbx_parser/FBXTokenizer.h new file mode 100644 index 0000000000..1761869c69 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXTokenizer.h @@ -0,0 +1,203 @@ +/*************************************************************************/ +/* FBXTokenizer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXTokenizer.h + * @brief FBX lexer + */ +#ifndef FBX_TOKENIZER_H +#define FBX_TOKENIZER_H + +#include "FBXParseTools.h" +#include "core/string/ustring.h" +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +namespace FBXDocParser { +/** Rough classification for text FBX tokens used for constructing the + * basic scope hierarchy. */ +enum TokenType { + // { + TokenType_OPEN_BRACKET = 0, + + // } + TokenType_CLOSE_BRACKET, + + // '"blablubb"', '2', '*14' - very general token class, + // further processing happens at a later stage. + TokenType_DATA, + + // + TokenType_BINARY_DATA, + + // , + TokenType_COMMA, + + // blubb: + TokenType_KEY +}; + +/** Represents a single token in a FBX file. Tokens are + * classified by the #TokenType enumerated types. + * + * Offers iterator protocol. Tokens are immutable. */ +class Token { +private: + static const unsigned int BINARY_MARKER = static_cast<unsigned int>(-1); + +public: + /** construct a textual token */ + Token(const char *p_sbegin, const char *p_send, TokenType p_type, unsigned int p_line, unsigned int p_column); + + /** construct a binary token */ + Token(const char *p_sbegin, const char *p_send, TokenType p_type, size_t p_offset); + ~Token(); + +public: + std::string StringContents() const { + return std::string(begin(), end()); + } + + bool IsBinary() const { + return column == BINARY_MARKER; + } + + const char *begin() const { + return sbegin; + } + + const char *end() const { + return send; + } + + TokenType Type() const { + return type; + } + + size_t Offset() const { + return offset; + } + + unsigned int Line() const { + return static_cast<unsigned int>(line); + } + + unsigned int Column() const { + return column; + } + +private: +#ifdef DEBUG_ENABLED + // full string copy for the sole purpose that it nicely appears + // in msvc's debugger window. + std::string contents; +#endif + + const char *sbegin = nullptr; + const char *send = nullptr; + const TokenType type; + + union { + size_t line; + size_t offset; + }; + const unsigned int column = 0; +}; + +// Fixed leak by using shared_ptr for tokens +typedef Token *TokenPtr; +typedef std::vector<TokenPtr> TokenList; + +#define new_Token new Token + +/** Main FBX tokenizer function. Transform input buffer into a list of preprocessed tokens. + * + * Skips over comments and generates line and column numbers. + * + * @param output_tokens Receives a list of all tokens in the input data. + * @param input_buffer Textual input buffer to be processed, 0-terminated. + * @print_error if something goes wrong */ +void Tokenize(TokenList &output_tokens, const char *input, size_t length); + +/** Tokenizer function for binary FBX files. + * + * Emits a token list suitable for direct parsing. + * + * @param output_tokens Receives a list of all tokens in the input data. + * @param input_buffer Binary input buffer to be processed. + * @param length Length of input buffer, in bytes. There is no 0-terminal. + * @print_error if something goes wrong */ +void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length); +} // namespace FBXDocParser + +#endif // FBX_TOKENIZER_H diff --git a/modules/fbx/fbx_parser/FBXUtil.cpp b/modules/fbx/fbx_parser/FBXUtil.cpp new file mode 100644 index 0000000000..508407ea51 --- /dev/null +++ b/modules/fbx/fbx_parser/FBXUtil.cpp @@ -0,0 +1,220 @@ +/*************************************************************************/ +/* FBXUtil.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXUtil.cpp + * @brief Implementation of internal FBX utility functions + */ + +#include "FBXUtil.h" +#include "FBXTokenizer.h" +#include <cstring> +#include <string> + +namespace FBXDocParser { +namespace Util { + +// ------------------------------------------------------------------------------------------------ +const char *TokenTypeString(TokenType t) { + switch (t) { + case TokenType_OPEN_BRACKET: + return "TOK_OPEN_BRACKET"; + + case TokenType_CLOSE_BRACKET: + return "TOK_CLOSE_BRACKET"; + + case TokenType_DATA: + return "TOK_DATA"; + + case TokenType_COMMA: + return "TOK_COMMA"; + + case TokenType_KEY: + return "TOK_KEY"; + + case TokenType_BINARY_DATA: + return "TOK_BINARY_DATA"; + } + + //ai_assert(false); + return ""; +} + +// Generated by this formula: T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; +static const uint8_t base64DecodeTable[128] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255 +}; + +uint8_t DecodeBase64(char ch) { + const auto idx = static_cast<uint8_t>(ch); + if (idx > 127) + return 255; + return base64DecodeTable[idx]; +} + +size_t ComputeDecodedSizeBase64(const char *in, size_t inLength) { + if (inLength < 2) { + return 0; + } + const size_t equals = size_t(in[inLength - 1] == '=') + size_t(in[inLength - 2] == '='); + const size_t full_length = (inLength * 3) >> 2; // div by 4 + if (full_length < equals) { + return 0; + } + return full_length - equals; +} + +size_t DecodeBase64(const char *in, size_t inLength, uint8_t *out, size_t maxOutLength) { + if (maxOutLength == 0 || inLength < 2) { + return 0; + } + const size_t realLength = inLength - size_t(in[inLength - 1] == '=') - size_t(in[inLength - 2] == '='); + size_t dst_offset = 0; + int val = 0, valb = -8; + for (size_t src_offset = 0; src_offset < realLength; ++src_offset) { + const uint8_t table_value = Util::DecodeBase64(in[src_offset]); + if (table_value == 255) { + return 0; + } + val = (val << 6) + table_value; + valb += 6; + if (valb >= 0) { + out[dst_offset++] = static_cast<uint8_t>((val >> valb) & 0xFF); + valb -= 8; + val &= 0xFFF; + } + } + return dst_offset; +} + +static const char to_base64_string[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +char EncodeBase64(char byte) { + return to_base64_string[(size_t)byte]; +} + +/** Encodes a block of 4 bytes to base64 encoding +* @param bytes Bytes to encode. +* @param out_string String to write encoded values to. +* @param string_pos Position in out_string. +*/ +void EncodeByteBlock(const char *bytes, std::string &out_string, size_t string_pos) { + char b0 = (bytes[0] & 0xFC) >> 2; + char b1 = (bytes[0] & 0x03) << 4 | ((bytes[1] & 0xF0) >> 4); + char b2 = (bytes[1] & 0x0F) << 2 | ((bytes[2] & 0xC0) >> 6); + char b3 = (bytes[2] & 0x3F); + + out_string[string_pos + 0] = EncodeBase64(b0); + out_string[string_pos + 1] = EncodeBase64(b1); + out_string[string_pos + 2] = EncodeBase64(b2); + out_string[string_pos + 3] = EncodeBase64(b3); +} + +std::string EncodeBase64(const char *data, size_t length) { + // calculate extra bytes needed to get a multiple of 3 + size_t extraBytes = 3 - length % 3; + + // number of base64 bytes + size_t encodedBytes = 4 * (length + extraBytes) / 3; + + std::string encoded_string(encodedBytes, '='); + + // read blocks of 3 bytes + for (size_t ib3 = 0; ib3 < length / 3; ib3++) { + const size_t iByte = ib3 * 3; + const size_t iEncodedByte = ib3 * 4; + const char *currData = &data[iByte]; + + EncodeByteBlock(currData, encoded_string, iEncodedByte); + } + + // if size of data is not a multiple of 3, also encode the final bytes (and add zeros where needed) + if (extraBytes > 0) { + char finalBytes[4] = { 0, 0, 0, 0 }; + memcpy(&finalBytes[0], &data[length - length % 3], length % 3); + + const size_t iEncodedByte = encodedBytes - 4; + EncodeByteBlock(&finalBytes[0], encoded_string, iEncodedByte); + + // add '=' at the end + for (size_t i = 0; i < 4 * extraBytes / 3; i++) + encoded_string[encodedBytes - i - 1] = '='; + } + return encoded_string; +} +} // namespace Util +} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXUtil.h b/modules/fbx/fbx_parser/FBXUtil.h new file mode 100644 index 0000000000..553d5fbc6b --- /dev/null +++ b/modules/fbx/fbx_parser/FBXUtil.h @@ -0,0 +1,122 @@ +/*************************************************************************/ +/* FBXUtil.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file FBXUtil.h + * @brief FBX utility functions for internal use + */ +#ifndef FBX_UTIL_H +#define FBX_UTIL_H + +#include "FBXTokenizer.h" +#include <stdint.h> + +namespace FBXDocParser { + +namespace Util { + +/** Get a string representation for a #TokenType. */ +const char *TokenTypeString(TokenType t); + +/** Decode a single Base64-encoded character. +* +* @param ch Character to decode (from base64 to binary). +* @return decoded byte value*/ +uint8_t DecodeBase64(char ch); + +/** Compute decoded size of a Base64-encoded string +* +* @param in Characters to decode. +* @param inLength Number of characters to decode. +* @return size of the decoded data (number of bytes)*/ +size_t ComputeDecodedSizeBase64(const char *in, size_t inLength); + +/** Decode a Base64-encoded string +* +* @param in Characters to decode. +* @param inLength Number of characters to decode. +* @param out Pointer where we will store the decoded data. +* @param maxOutLength Size of output buffer. +* @return size of the decoded data (number of bytes)*/ +size_t DecodeBase64(const char *in, size_t inLength, uint8_t *out, size_t maxOutLength); + +char EncodeBase64(char byte); + +/** Encode bytes in base64-encoding +* +* @param data Binary data to encode. +* @param inLength Number of bytes to encode. +* @return base64-encoded string*/ +std::string EncodeBase64(const char *data, size_t length); +} // namespace Util +} // namespace FBXDocParser + +#endif // FBX_UTIL_H diff --git a/modules/fbx/fbx_parser/LICENSE b/modules/fbx/fbx_parser/LICENSE new file mode 100644 index 0000000000..b42fc6efe6 --- /dev/null +++ b/modules/fbx/fbx_parser/LICENSE @@ -0,0 +1,39 @@ +The files in this folder were originally from ASSIMP, but have been heavily modified to fix bugs and match coding +conventions of the Godot Engine project. We have kept a copy of the applicable licenses in the folder as required by +the license. + +Open Asset Import Library (assimp) + +Copyright (c) 2006-2020, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/modules/assimp/register_types.cpp b/modules/fbx/register_types.cpp index 6cb0fc982f..28fa69282e 100644 --- a/modules/assimp/register_types.cpp +++ b/modules/fbx/register_types.cpp @@ -31,22 +31,22 @@ #include "register_types.h" #include "editor/editor_node.h" -#include "editor_scene_importer_assimp.h" +#include "editor_scene_importer_fbx.h" #ifdef TOOLS_ENABLED static void _editor_init() { - Ref<EditorSceneImporterAssimp> import_assimp; - import_assimp.instance(); - ResourceImporterScene::get_singleton()->add_importer(import_assimp); + Ref<EditorSceneImporterFBX> import_fbx; + import_fbx.instance(); + ResourceImporterScene::get_singleton()->add_importer(import_fbx); } #endif -void register_assimp_types() { +void register_fbx_types() { #ifdef TOOLS_ENABLED ClassDB::APIType prev_api = ClassDB::get_current_api(); ClassDB::set_current_api(ClassDB::API_EDITOR); - ClassDB::register_class<EditorSceneImporterAssimp>(); + ClassDB::register_class<EditorSceneImporterFBX>(); ClassDB::set_current_api(prev_api); @@ -54,5 +54,5 @@ void register_assimp_types() { #endif } -void unregister_assimp_types() { +void unregister_fbx_types() { } diff --git a/modules/assimp/register_types.h b/modules/fbx/register_types.h index f399a7acc6..26328c4c15 100644 --- a/modules/assimp/register_types.h +++ b/modules/fbx/register_types.h @@ -28,10 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef ASSIMP_REGISTER_TYPES_H -#define ASSIMP_REGISTER_TYPES_H +#ifndef FBX_REGISTER_TYPES_H +#define FBX_REGISTER_TYPES_H -void register_assimp_types(); -void unregister_assimp_types(); +void register_fbx_types(); +void unregister_fbx_types(); -#endif // ASSIMP_REGISTER_TYPES_H +#endif // FBX_REGISTER_TYPES_H diff --git a/modules/fbx/tools/import_utils.cpp b/modules/fbx/tools/import_utils.cpp new file mode 100644 index 0000000000..5c9e433ab8 --- /dev/null +++ b/modules/fbx/tools/import_utils.cpp @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* import_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "import_utils.h" + +Vector3 ImportUtils::deg2rad(const Vector3 &p_rotation) { + return p_rotation / 180.0 * Math_PI; +} + +Vector3 ImportUtils::rad2deg(const Vector3 &p_rotation) { + return p_rotation / Math_PI * 180.0; +} + +Basis ImportUtils::EulerToBasis(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation) { + Basis ret; + + // FBX is using intrinsic euler, we can convert intrinsic to extrinsic (the one used in godot + // by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf + switch (mode) { + case FBXDocParser::Model::RotOrder_EulerXYZ: + ret.set_euler_zyx(p_rotation); + break; + + case FBXDocParser::Model::RotOrder_EulerXZY: + ret.set_euler_yzx(p_rotation); + break; + + case FBXDocParser::Model::RotOrder_EulerYZX: + ret.set_euler_xzy(p_rotation); + break; + + case FBXDocParser::Model::RotOrder_EulerYXZ: + ret.set_euler_zxy(p_rotation); + break; + + case FBXDocParser::Model::RotOrder_EulerZXY: + ret.set_euler_yxz(p_rotation); + break; + + case FBXDocParser::Model::RotOrder_EulerZYX: + ret.set_euler_xyz(p_rotation); + break; + + case FBXDocParser::Model::RotOrder_SphericXYZ: + // TODO do this. + break; + + default: + // If you land here, Please integrate all enums. + CRASH_NOW_MSG("This is not unreachable."); + } + + return ret; +} + +Quat ImportUtils::EulerToQuaternion(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation) { + return ImportUtils::EulerToBasis(mode, p_rotation); +} + +Vector3 ImportUtils::BasisToEuler(FBXDocParser::Model::RotOrder mode, const Basis &p_rotation) { + // FBX is using intrinsic euler, we can convert intrinsic to extrinsic (the one used in godot + // by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf + switch (mode) { + case FBXDocParser::Model::RotOrder_EulerXYZ: + return p_rotation.get_euler_zyx(); + + case FBXDocParser::Model::RotOrder_EulerXZY: + return p_rotation.get_euler_yzx(); + + case FBXDocParser::Model::RotOrder_EulerYZX: + return p_rotation.get_euler_xzy(); + + case FBXDocParser::Model::RotOrder_EulerYXZ: + return p_rotation.get_euler_zxy(); + + case FBXDocParser::Model::RotOrder_EulerZXY: + return p_rotation.get_euler_yxz(); + + case FBXDocParser::Model::RotOrder_EulerZYX: + return p_rotation.get_euler_xyz(); + + case FBXDocParser::Model::RotOrder_SphericXYZ: + // TODO + return Vector3(); + + default: + // If you land here, Please integrate all enums. + CRASH_NOW_MSG("This is not unreachable."); + return Vector3(); + } +} + +Vector3 ImportUtils::QuaternionToEuler(FBXDocParser::Model::RotOrder mode, const Quat &p_rotation) { + return BasisToEuler(mode, p_rotation); +} + +Transform get_unscaled_transform(const Transform &p_initial, real_t p_scale) { + Transform unscaled = Transform(p_initial.basis, p_initial.origin * p_scale); + ERR_FAIL_COND_V_MSG(unscaled.basis.determinant() == 0, Transform(), "det is zero unscaled?"); + return unscaled; +} + +Vector3 get_poly_normal(const std::vector<Vector3> &p_vertices) { + ERR_FAIL_COND_V_MSG(p_vertices.size() < 3, Vector3(0, 0, 0), "At least 3 vertices are necesary"); + // Using long double to make sure that normal is computed for even really tiny objects. + typedef long double ldouble; + ldouble x = 0.0; + ldouble y = 0.0; + ldouble z = 0.0; + for (size_t i = 0; i < p_vertices.size(); i += 1) { + const Vector3 current = p_vertices[i]; + const Vector3 next = p_vertices[(i + 1) % p_vertices.size()]; + x += (ldouble(current.y) - ldouble(next.y)) * (ldouble(current.z) + ldouble(next.z)); + y += (ldouble(current.z) - ldouble(next.z)) * (ldouble(current.x) + ldouble(next.x)); + z += (ldouble(current.x) - ldouble(next.x)) * (ldouble(current.y) + ldouble(next.y)); + } + const ldouble l2 = x * x + y * y + z * z; + if (l2 == 0.0) { + return (p_vertices[0] - p_vertices[1]).normalized().cross((p_vertices[0] - p_vertices[2]).normalized()).normalized(); + } else { + const double l = Math::sqrt(double(l2)); + return Vector3(x / l, y / l, z / l); + } +} diff --git a/modules/fbx/tools/import_utils.h b/modules/fbx/tools/import_utils.h new file mode 100644 index 0000000000..98a0cef908 --- /dev/null +++ b/modules/fbx/tools/import_utils.h @@ -0,0 +1,400 @@ +/*************************************************************************/ +/* import_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 IMPORT_UTILS_FBX_IMPORTER_H +#define IMPORT_UTILS_FBX_IMPORTER_H + +#include "core/io/image_loader.h" + +#include "data/import_state.h" +#include "fbx_parser/FBXDocument.h" + +#include <string> + +#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL + +/** + * Import Utils + * Conversion tools / glue code to convert from FBX to Godot +*/ +class ImportUtils { +public: + /// Convert a vector from degrees to radians. + static Vector3 deg2rad(const Vector3 &p_rotation); + + /// Convert a vector from radians to degrees. + static Vector3 rad2deg(const Vector3 &p_rotation); + + /// Converts rotation order vector (in rad) to quaternion. + static Basis EulerToBasis(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation); + + /// Converts rotation order vector (in rad) to quaternion. + static Quat EulerToQuaternion(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation); + + /// Converts basis into rotation order vector (in rad). + static Vector3 BasisToEuler(FBXDocParser::Model::RotOrder mode, const Basis &p_rotation); + + /// Converts quaternion into rotation order vector (in rad). + static Vector3 QuaternionToEuler(FBXDocParser::Model::RotOrder mode, const Quat &p_rotation); + + static void debug_xform(String name, const Transform &t) { + print_verbose(name + " " + t.origin + " rotation: " + (t.basis.get_euler() * (180 / Math_PI))); + } + + static String FBXNodeToName(const std::string &name) { + // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if + // this causes ambiguities, well possible between empty identifiers, + // such as "Model::" and ""). Make sure the behaviour is consistent + // across multiple calls to FixNodeName(). + + // We must remove this from the name + // Some bones have this + // SubDeformer:: + // Meshes, Joints have this, some other IK elements too. + // Model:: + + String node_name = String(name.c_str()); + + if (node_name.substr(0, 7) == "Model::") { + node_name = node_name.substr(7, node_name.length() - 7); + return node_name.replace(":", ""); + } + + if (node_name.substr(0, 13) == "SubDeformer::") { + node_name = node_name.substr(13, node_name.length() - 13); + return node_name.replace(":", ""); + } + + if (node_name.substr(0, 11) == "AnimStack::") { + node_name = node_name.substr(11, node_name.length() - 11); + return node_name.replace(":", ""); + } + + if (node_name.substr(0, 15) == "AnimCurveNode::") { + node_name = node_name.substr(15, node_name.length() - 15); + return node_name.replace(":", ""); + } + + if (node_name.substr(0, 11) == "AnimCurve::") { + node_name = node_name.substr(11, node_name.length() - 11); + return node_name.replace(":", ""); + } + + if (node_name.substr(0, 10) == "Geometry::") { + node_name = node_name.substr(10, node_name.length() - 10); + return node_name.replace(":", ""); + } + + if (node_name.substr(0, 10) == "Material::") { + node_name = node_name.substr(10, node_name.length() - 10); + return node_name.replace(":", ""); + } + + if (node_name.substr(0, 9) == "Texture::") { + node_name = node_name.substr(9, node_name.length() - 9); + return node_name.replace(":", ""); + } + + return node_name.replace(":", ""); + } + + static std::string FBXAnimMeshName(const std::string &name) { + if (name.length()) { + size_t indexOf = name.find_first_of("::"); + if (indexOf != std::string::npos && indexOf < name.size() - 2) { + return name.substr(indexOf + 2); + } + } + return name.length() ? name : "AnimMesh"; + } + + static Vector3 safe_import_vector3(const Vector3 &p_vec) { + Vector3 vector = p_vec; + if (Math::is_equal_approx(0, vector.x)) { + vector.x = 0; + } + + if (Math::is_equal_approx(0, vector.y)) { + vector.y = 0; + } + + if (Math::is_equal_approx(0, vector.z)) { + vector.z = 0; + } + return vector; + } + + static void debug_xform(String name, const Basis &t) { + //print_verbose(name + " rotation: " + (t.get_euler() * (180 / Math_PI))); + } + + static Vector3 FixAxisConversions(Vector3 input) { + return Vector3(input.x, input.y, input.z); + } + + static void AlignMeshAxes(std::vector<Vector3> &vertex_data) { + for (size_t x = 0; x < vertex_data.size(); x++) { + vertex_data[x] = FixAxisConversions(vertex_data[x]); + } + } + + struct AssetImportFbx { + enum ETimeMode { + TIME_MODE_DEFAULT = 0, + TIME_MODE_120 = 1, + TIME_MODE_100 = 2, + TIME_MODE_60 = 3, + TIME_MODE_50 = 4, + TIME_MODE_48 = 5, + TIME_MODE_30 = 6, + TIME_MODE_30_DROP = 7, + TIME_MODE_NTSC_DROP_FRAME = 8, + TIME_MODE_NTSC_FULL_FRAME = 9, + TIME_MODE_PAL = 10, + TIME_MODE_CINEMA = 11, + TIME_MODE_1000 = 12, + TIME_MODE_CINEMA_ND = 13, + TIME_MODE_CUSTOM = 14, + TIME_MODE_TIME_MODE_COUNT = 15 + }; + enum UpAxis { + UP_VECTOR_AXIS_X = 1, + UP_VECTOR_AXIS_Y = 2, + UP_VECTOR_AXIS_Z = 3 + }; + enum FrontAxis { + FRONT_PARITY_EVEN = 1, + FRONT_PARITY_ODD = 2, + }; + + enum CoordAxis { + COORD_RIGHT = 0, + COORD_LEFT = 1 + }; + }; + + /** Get fbx fps for time mode meta data + */ + static float get_fbx_fps(int32_t time_mode) { + switch (time_mode) { + case AssetImportFbx::TIME_MODE_DEFAULT: + return 24; + case AssetImportFbx::TIME_MODE_120: + return 120; + case AssetImportFbx::TIME_MODE_100: + return 100; + case AssetImportFbx::TIME_MODE_60: + return 60; + case AssetImportFbx::TIME_MODE_50: + return 50; + case AssetImportFbx::TIME_MODE_48: + return 48; + case AssetImportFbx::TIME_MODE_30: + return 30; + case AssetImportFbx::TIME_MODE_30_DROP: + return 30; + case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: + return 29.9700262f; + case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: + return 29.9700262f; + case AssetImportFbx::TIME_MODE_PAL: + return 25; + case AssetImportFbx::TIME_MODE_CINEMA: + return 24; + case AssetImportFbx::TIME_MODE_1000: + return 1000; + case AssetImportFbx::TIME_MODE_CINEMA_ND: + return 23.976f; + case AssetImportFbx::TIME_MODE_CUSTOM: + return -1; + } + return 0; + } + + static float get_fbx_fps(const FBXDocParser::FileGlobalSettings *FBXSettings) { + int time_mode = FBXSettings->TimeMode(); + + // get the animation FPS + float frames_per_second = get_fbx_fps(time_mode); + + // handle animation custom FPS time. + if (time_mode == ImportUtils::AssetImportFbx::TIME_MODE_CUSTOM) { + print_verbose("FBX Animation has custom FPS setting"); + frames_per_second = FBXSettings->CustomFrameRate(); + + // not our problem this is the modeller, we can print as an error so they can fix the source. + if (frames_per_second == 0) { + print_error("Custom animation time in file is set to 0 value, animation won't play, please edit your file to correct the FPS value"); + } + } + return frames_per_second; + } + + /** + * Find hardcoded textures from assimp which could be in many different directories + */ + + /** + * set_texture_mapping_mode + * Helper to check the mapping mode of the texture (repeat, clamp and mirror) + */ + // static void set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<ImageTexture> texture) { + // ERR_FAIL_COND(texture.is_null()); + // ERR_FAIL_COND(map_mode == NULL); + // aiTextureMapMode tex_mode = map_mode[0]; + + // int32_t flags = Texture::FLAGS_DEFAULT; + // if (tex_mode == aiTextureMapMode_Wrap) { + // //Default + // } else if (tex_mode == aiTextureMapMode_Clamp) { + // flags = flags & ~Texture::FLAG_REPEAT; + // } else if (tex_mode == aiTextureMapMode_Mirror) { + // flags = flags | Texture::FLAG_MIRRORED_REPEAT; + // } + // texture->set_flags(flags); + // } + + /** + * Load or load from cache image :) + * We need to upgrade this in the later version :) should not be hard + */ + //static Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path){ + // Map<String, Ref<Image> >::Element *match = state.path_to_image_cache.find(p_path); + + // // if our cache contains this image then don't bother + // if (match) { + // return match->get(); + // } + + // Vector<String> split_path = p_path.get_basename().split("*"); + // if (split_path.size() == 2) { + // size_t texture_idx = split_path[1].to_int(); + // ERR_FAIL_COND_V(texture_idx >= p_scene->mNumTextures, Ref<Image>()); + // aiTexture *tex = p_scene->mTextures[texture_idx]; + // String filename = AssimpUtils::get_raw_string_from_assimp(tex->mFilename); + // filename = filename.get_file(); + // print_verbose("Open Asset Import: Loading embedded texture " + filename); + // if (tex->mHeight == 0) { + // if (tex->CheckFormat("png")) { + // Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + // ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + // state.path_to_image_cache.insert(p_path, img); + // return img; + // } else if (tex->CheckFormat("jpg")) { + // Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + // ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + // state.path_to_image_cache.insert(p_path, img); + // return img; + // } else if (tex->CheckFormat("dds")) { + // ERR_FAIL_COND_V_MSG(true, Ref<Image>(), "Open Asset Import: Embedded dds not implemented"); + // } + // } else { + // Ref<Image> img; + // img.instance(); + // PoolByteArray arr; + // uint32_t size = tex->mWidth * tex->mHeight; + // arr.resize(size); + // memcpy(arr.write().ptr(), tex->pcData, size); + // ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Image>()); + // //ARGB8888 to RGBA8888 + // for (int32_t i = 0; i < arr.size() / 4; i++) { + // arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0]; + // arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1]; + // arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2]; + // arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3]; + // } + // img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr); + // ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + // state.path_to_image_cache.insert(p_path, img); + // return img; + // } + // return Ref<Image>(); + // } else { + // Ref<Texture> texture = ResourceLoader::load(p_path); + // ERR_FAIL_COND_V(texture.is_null(), Ref<Image>()); + // Ref<Image> image = texture->get_data(); + // ERR_FAIL_COND_V(image.is_null(), Ref<Image>()); + // state.path_to_image_cache.insert(p_path, image); + // return image; + // } + + // return Ref<Image>(); + //} + + // /* create texture from assimp data, if found in path */ + // static bool CreateAssimpTexture( + // AssimpImporter::ImportState &state, + // aiString texture_path, + // String &filename, + // String &path, + // AssimpImageData &image_state) { + // filename = get_raw_string_from_assimp(texture_path); + // path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); + // bool found = false; + // find_texture_path(state.path, path, found); + // if (found) { + // image_state.raw_image = AssimpUtils::load_image(state, state.assimp_scene, path); + // if (image_state.raw_image.is_valid()) { + // image_state.texture.instance(); + // image_state.texture->create_from_image(image_state.raw_image); + // image_state.texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); + // return true; + // } + // } + + // return false; + // } + // /** GetAssimpTexture + // * Designed to retrieve textures for you + // */ + // static bool GetAssimpTexture( + // AssimpImporter::ImportState &state, + // aiMaterial *ai_material, + // aiTextureType texture_type, + // String &filename, + // String &path, + // AssimpImageData &image_state) { + // aiString ai_filename = aiString(); + // if (AI_SUCCESS == ai_material->GetTexture(texture_type, 0, &ai_filename, NULL, NULL, NULL, NULL, image_state.map_mode)) { + // return CreateAssimpTexture(state, ai_filename, filename, path, image_state); + // } + + // return false; + // } +}; + +// Apply the transforms so the basis will have scale 1. +Transform get_unscaled_transform(const Transform &p_initial, real_t p_scale); + +/// Uses the Newell's method to compute any polygon normal. +/// The polygon must be at least size of 3 or bigger. +Vector3 get_poly_normal(const std::vector<Vector3> &p_vertices); + +#endif // IMPORT_UTILS_FBX_IMPORTER_H diff --git a/modules/fbx/tools/validation_tools.cpp b/modules/fbx/tools/validation_tools.cpp new file mode 100644 index 0000000000..0c4d7abe8d --- /dev/null +++ b/modules/fbx/tools/validation_tools.cpp @@ -0,0 +1,48 @@ +/*************************************************************************/ +/* validation_tools.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "validation_tools.h" + +#ifdef TOOLS_ENABLED + +#include "core/string/print_string.h" +#include "core/string/ustring.h" + +ValidationTracker::Entries *ValidationTracker::entries_singleton = memnew(ValidationTracker::Entries); + +// for printing our CSV to dump validation problems of files +// later we can make some agnostic tooling for this but this is fine for the time being. +void ValidationTracker::Entries::add_validation_error(String asset_path, String message) { + print_error(message); + // note: implementation is static + validation_entries[asset_path].push_back(message); +} + +#endif // TOOLS_ENABLED diff --git a/modules/fbx/tools/validation_tools.h b/modules/fbx/tools/validation_tools.h new file mode 100644 index 0000000000..649842a1cb --- /dev/null +++ b/modules/fbx/tools/validation_tools.h @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* validation_tools.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 FBX_VALIDATION_TOOLS_H +#define FBX_VALIDATION_TOOLS_H + +#ifdef TOOLS_ENABLED + +#include "core/io/json.h" +#include "core/os/file_access.h" +#include "core/string/ustring.h" +#include "core/templates/local_vector.h" +#include "core/templates/map.h" + +class ValidationTracker { +protected: + struct Entries { + Map<String, LocalVector<String>> validation_entries = Map<String, LocalVector<String>>(); + + // for printing our CSV to dump validation problems of files + // later we can make some agnostic tooling for this but this is fine for the time being. + void add_validation_error(String asset_path, String message); + void print_to_csv() { + print_verbose("Exporting assset validation log please wait"); + String massive_log_file; + + String csv_header = "file_path, error message, extra data\n"; + massive_log_file += csv_header; + + for (Map<String, LocalVector<String>>::Element *element = validation_entries.front(); element; element = element->next()) { + for (unsigned int x = 0; x < element->value().size(); x++) { + const String &line_entry = element->key() + ", " + element->value()[x].c_escape() + "\n"; + massive_log_file += line_entry; + } + } + + String path = "asset_validation_errors.csv"; + Error err; + FileAccess *file = FileAccess::open(path, FileAccess::WRITE, &err); + if (!file || err) { + if (file) + memdelete(file); + print_error("ValidationTracker Error - failed to create file - path: %s\n" + path); + return; + } + + file->store_string(massive_log_file); + if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { + print_error("ValidationTracker Error - failed to write to file - path: %s\n" + path); + } + file->close(); + memdelete(file); + } + }; + // asset path, error messages + static Entries *entries_singleton; + +public: + static Entries *get_singleton() { + return entries_singleton; + } +}; + +#endif // TOOLS_ENABLED +#endif // FBX_VALIDATION_TOOLS_H diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index bfc1658bb4..fc2535a6ca 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_freetype = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + if env["builtin_freetype"]: thirdparty_dir = "#thirdparty/freetype/" thirdparty_sources = [ @@ -84,6 +87,7 @@ if env["builtin_freetype"]: env_thirdparty = env_freetype.Clone() env_thirdparty.disable_warnings() lib = env_thirdparty.add_library("freetype_builtin", thirdparty_sources) + thirdparty_obj += lib # Needs to be appended to arrive after libscene in the linker call, # but we don't want it to arrive *after* system libs, so manual hack @@ -98,5 +102,13 @@ if env["builtin_freetype"]: if not inserted: env.Append(LIBS=[lib]) + # Godot source files -env_freetype.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_freetype.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index f397ee96c5..bb72dd69b8 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -286,7 +286,7 @@ bool GDNative::initialize() { } String lib_path = library->get_current_library_path(); - if (lib_path.empty()) { + if (lib_path.is_empty()) { ERR_PRINT("No library set for this platform"); return false; } diff --git a/modules/gdnative/gdnative/array.cpp b/modules/gdnative/gdnative/array.cpp index 863889acbc..403d651165 100644 --- a/modules/gdnative/gdnative/array.cpp +++ b/modules/gdnative/gdnative/array.cpp @@ -215,9 +215,9 @@ godot_int GDAPI godot_array_count(const godot_array *p_self, const godot_variant return self->count(*val); } -godot_bool GDAPI godot_array_empty(const godot_array *p_self) { +godot_bool GDAPI godot_array_is_empty(const godot_array *p_self) { const Array *self = (const Array *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_array_erase(godot_array *p_self, const godot_variant *p_value) { diff --git a/modules/gdnative/gdnative/dictionary.cpp b/modules/gdnative/gdnative/dictionary.cpp index b6900b28bb..b34d73dc40 100644 --- a/modules/gdnative/gdnative/dictionary.cpp +++ b/modules/gdnative/gdnative/dictionary.cpp @@ -71,9 +71,9 @@ godot_int GDAPI godot_dictionary_size(const godot_dictionary *p_self) { return self->size(); } -godot_bool GDAPI godot_dictionary_empty(const godot_dictionary *p_self) { +godot_bool GDAPI godot_dictionary_is_empty(const godot_dictionary *p_self) { const Dictionary *self = (const Dictionary *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_dictionary_clear(godot_dictionary *p_self) { diff --git a/modules/gdnative/gdnative/packed_arrays.cpp b/modules/gdnative/gdnative/packed_arrays.cpp index cc1e05b8a4..351c456a5d 100644 --- a/modules/gdnative/gdnative/packed_arrays.cpp +++ b/modules/gdnative/gdnative/packed_arrays.cpp @@ -150,9 +150,9 @@ godot_int GDAPI godot_packed_byte_array_size(const godot_packed_byte_array *p_se return self->size(); } -godot_bool GDAPI godot_packed_byte_array_empty(const godot_packed_byte_array *p_self) { +godot_bool GDAPI godot_packed_byte_array_is_empty(const godot_packed_byte_array *p_self) { const Vector<uint8_t> *self = (const Vector<uint8_t> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_byte_array_destroy(godot_packed_byte_array *p_self) { @@ -254,9 +254,9 @@ godot_int GDAPI godot_packed_int32_array_size(const godot_packed_int32_array *p_ return self->size(); } -godot_bool GDAPI godot_packed_int32_array_empty(const godot_packed_int32_array *p_self) { +godot_bool GDAPI godot_packed_int32_array_is_empty(const godot_packed_int32_array *p_self) { const Vector<int32_t> *self = (const Vector<int32_t> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_int32_array_destroy(godot_packed_int32_array *p_self) { @@ -358,9 +358,9 @@ godot_int GDAPI godot_packed_int64_array_size(const godot_packed_int64_array *p_ return self->size(); } -godot_bool GDAPI godot_packed_int64_array_empty(const godot_packed_int64_array *p_self) { +godot_bool GDAPI godot_packed_int64_array_is_empty(const godot_packed_int64_array *p_self) { const Vector<int64_t> *self = (const Vector<int64_t> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_int64_array_destroy(godot_packed_int64_array *p_self) { @@ -462,9 +462,9 @@ godot_int GDAPI godot_packed_float32_array_size(const godot_packed_float32_array return self->size(); } -godot_bool GDAPI godot_packed_float32_array_empty(const godot_packed_float32_array *p_self) { +godot_bool GDAPI godot_packed_float32_array_is_empty(const godot_packed_float32_array *p_self) { const Vector<float> *self = (const Vector<float> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_float32_array_destroy(godot_packed_float32_array *p_self) { @@ -566,9 +566,9 @@ godot_int GDAPI godot_packed_float64_array_size(const godot_packed_float64_array return self->size(); } -godot_bool GDAPI godot_packed_float64_array_empty(const godot_packed_float64_array *p_self) { +godot_bool GDAPI godot_packed_float64_array_is_empty(const godot_packed_float64_array *p_self) { const Vector<double> *self = (const Vector<double> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_float64_array_destroy(godot_packed_float64_array *p_self) { @@ -679,9 +679,9 @@ godot_int GDAPI godot_packed_string_array_size(const godot_packed_string_array * return self->size(); } -godot_bool GDAPI godot_packed_string_array_empty(const godot_packed_string_array *p_self) { +godot_bool GDAPI godot_packed_string_array_is_empty(const godot_packed_string_array *p_self) { const Vector<String> *self = (const Vector<String> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_string_array_destroy(godot_packed_string_array *p_self) { @@ -791,9 +791,9 @@ godot_int GDAPI godot_packed_vector2_array_size(const godot_packed_vector2_array return self->size(); } -godot_bool GDAPI godot_packed_vector2_array_empty(const godot_packed_vector2_array *p_self) { +godot_bool GDAPI godot_packed_vector2_array_is_empty(const godot_packed_vector2_array *p_self) { const Vector<Vector2> *self = (const Vector<Vector2> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_vector2_array_destroy(godot_packed_vector2_array *p_self) { @@ -903,9 +903,9 @@ godot_int GDAPI godot_packed_vector2i_array_size(const godot_packed_vector2i_arr return self->size(); } -godot_bool GDAPI godot_packed_vector2i_array_empty(const godot_packed_vector2i_array *p_self) { +godot_bool GDAPI godot_packed_vector2i_array_is_empty(const godot_packed_vector2i_array *p_self) { const Vector<Vector2i> *self = (const Vector<Vector2i> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_vector2i_array_destroy(godot_packed_vector2i_array *p_self) { @@ -1015,9 +1015,9 @@ godot_int GDAPI godot_packed_vector3_array_size(const godot_packed_vector3_array return self->size(); } -godot_bool GDAPI godot_packed_vector3_array_empty(const godot_packed_vector3_array *p_self) { +godot_bool GDAPI godot_packed_vector3_array_is_empty(const godot_packed_vector3_array *p_self) { const Vector<Vector3> *self = (const Vector<Vector3> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_vector3_array_destroy(godot_packed_vector3_array *p_self) { @@ -1127,9 +1127,9 @@ godot_int GDAPI godot_packed_color_array_size(const godot_packed_color_array *p_ return self->size(); } -godot_bool GDAPI godot_packed_color_array_empty(const godot_packed_color_array *p_self) { +godot_bool GDAPI godot_packed_color_array_is_empty(const godot_packed_color_array *p_self) { const Vector<Color> *self = (const Vector<Color> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_color_array_destroy(godot_packed_color_array *p_self) { diff --git a/modules/gdnative/gdnative/rect2.cpp b/modules/gdnative/gdnative/rect2.cpp index bacefced5d..aeb034a4c0 100644 --- a/modules/gdnative/gdnative/rect2.cpp +++ b/modules/gdnative/gdnative/rect2.cpp @@ -90,11 +90,11 @@ godot_bool GDAPI godot_rect2_has_no_area(const godot_rect2 *p_self) { return self->has_no_area(); } -godot_rect2 GDAPI godot_rect2_clip(const godot_rect2 *p_self, const godot_rect2 *p_b) { +godot_rect2 GDAPI godot_rect2_intersection(const godot_rect2 *p_self, const godot_rect2 *p_b) { godot_rect2 dest; const Rect2 *self = (const Rect2 *)p_self; const Rect2 *b = (const Rect2 *)p_b; - *((Rect2 *)&dest) = self->clip(*b); + *((Rect2 *)&dest) = self->intersection(*b); return dest; } @@ -127,10 +127,10 @@ godot_rect2 GDAPI godot_rect2_grow_individual(const godot_rect2 *p_self, const g return dest; } -godot_rect2 GDAPI godot_rect2_grow_margin(const godot_rect2 *p_self, const godot_int p_margin, const godot_real p_by) { +godot_rect2 GDAPI godot_rect2_grow_side(const godot_rect2 *p_self, const godot_int p_side, const godot_real p_by) { godot_rect2 dest; const Rect2 *self = (const Rect2 *)p_self; - *((Rect2 *)&dest) = self->grow_margin((Margin)p_margin, p_by); + *((Rect2 *)&dest) = self->grow_side((Side)p_side, p_by); return dest; } @@ -233,11 +233,11 @@ godot_bool GDAPI godot_rect2i_has_no_area(const godot_rect2i *p_self) { return self->has_no_area(); } -godot_rect2i GDAPI godot_rect2i_clip(const godot_rect2i *p_self, const godot_rect2i *p_b) { +godot_rect2i GDAPI godot_rect2i_intersection(const godot_rect2i *p_self, const godot_rect2i *p_b) { godot_rect2i dest; const Rect2i *self = (const Rect2i *)p_self; const Rect2i *b = (const Rect2i *)p_b; - *((Rect2i *)&dest) = self->clip(*b); + *((Rect2i *)&dest) = self->intersection(*b); return dest; } @@ -270,10 +270,10 @@ godot_rect2i GDAPI godot_rect2i_grow_individual(const godot_rect2i *p_self, cons return dest; } -godot_rect2i GDAPI godot_rect2i_grow_margin(const godot_rect2i *p_self, const godot_int p_margin, const godot_int p_by) { +godot_rect2i GDAPI godot_rect2i_grow_side(const godot_rect2i *p_self, const godot_int p_side, const godot_int p_by) { godot_rect2i dest; const Rect2i *self = (const Rect2i *)p_self; - *((Rect2i *)&dest) = self->grow_margin((Margin)p_margin, p_by); + *((Rect2i *)&dest) = self->grow_side((Side)p_side, p_by); return dest; } diff --git a/modules/gdnative/gdnative/string.cpp b/modules/gdnative/gdnative/string.cpp index 47c7f7b6e7..743c47c01a 100644 --- a/modules/gdnative/gdnative/string.cpp +++ b/modules/gdnative/gdnative/string.cpp @@ -1087,10 +1087,10 @@ godot_string GDAPI godot_string_sha256_text(const godot_string *p_self) { return result; } -godot_bool godot_string_empty(const godot_string *p_self) { +godot_bool godot_string_is_empty(const godot_string *p_self) { const String *self = (const String *)p_self; - return self->empty(); + return self->is_empty(); } // path functions diff --git a/modules/gdnative/gdnative/vector2.cpp b/modules/gdnative/gdnative/vector2.cpp index 1ba846d315..0c2a414c08 100644 --- a/modules/gdnative/gdnative/vector2.cpp +++ b/modules/gdnative/gdnative/vector2.cpp @@ -154,10 +154,10 @@ godot_vector2 GDAPI godot_vector2_rotated(const godot_vector2 *p_self, const god return dest; } -godot_vector2 GDAPI godot_vector2_tangent(const godot_vector2 *p_self) { +godot_vector2 GDAPI godot_vector2_orthogonal(const godot_vector2 *p_self) { godot_vector2 dest; const Vector2 *self = (const Vector2 *)p_self; - *((Vector2 *)&dest) = self->tangent(); + *((Vector2 *)&dest) = self->orthogonal(); return dest; } diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 84b36b3745..e104c77eae 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -380,7 +380,7 @@ ] }, { - "name": "godot_array_empty", + "name": "godot_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_array *", "p_self"] @@ -1388,7 +1388,7 @@ ] }, { - "name": "godot_dictionary_empty", + "name": "godot_dictionary_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_dictionary *", "p_self"] @@ -1647,7 +1647,7 @@ ] }, { - "name": "godot_packed_byte_array_empty", + "name": "godot_packed_byte_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_byte_array *", "p_self"] @@ -1793,7 +1793,7 @@ ] }, { - "name": "godot_packed_int32_array_empty", + "name": "godot_packed_int32_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_int32_array *", "p_self"] @@ -1939,7 +1939,7 @@ ] }, { - "name": "godot_packed_int64_array_empty", + "name": "godot_packed_int64_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_int64_array *", "p_self"] @@ -2085,7 +2085,7 @@ ] }, { - "name": "godot_packed_float32_array_empty", + "name": "godot_packed_float32_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_float32_array *", "p_self"] @@ -2231,7 +2231,7 @@ ] }, { - "name": "godot_packed_float64_array_empty", + "name": "godot_packed_float64_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_float64_array *", "p_self"] @@ -2377,7 +2377,7 @@ ] }, { - "name": "godot_packed_string_array_empty", + "name": "godot_packed_string_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_string_array *", "p_self"] @@ -2523,7 +2523,7 @@ ] }, { - "name": "godot_packed_vector2_array_empty", + "name": "godot_packed_vector2_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_vector2_array *", "p_self"] @@ -2669,7 +2669,7 @@ ] }, { - "name": "godot_packed_vector2i_array_empty", + "name": "godot_packed_vector2i_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_vector2i_array *", "p_self"] @@ -2815,7 +2815,7 @@ ] }, { - "name": "godot_packed_vector3_array_empty", + "name": "godot_packed_vector3_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_vector3_array *", "p_self"] @@ -2961,7 +2961,7 @@ ] }, { - "name": "godot_packed_color_array_empty", + "name": "godot_packed_color_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_color_array *", "p_self"] @@ -3534,11 +3534,11 @@ ] }, { - "name": "godot_rect2_grow_margin", + "name": "godot_rect2_grow_side", "return_type": "godot_rect2", "arguments": [ ["const godot_rect2 *", "p_self"], - ["const godot_int", "p_margin"], + ["const godot_int", "p_side"], ["const godot_real", "p_by"] ] }, @@ -3573,7 +3573,7 @@ ] }, { - "name": "godot_rect2_clip", + "name": "godot_rect2_intersection", "return_type": "godot_rect2", "arguments": [ ["const godot_rect2 *", "p_self"], @@ -3715,7 +3715,7 @@ ] }, { - "name": "godot_rect2i_clip", + "name": "godot_rect2i_intersection", "return_type": "godot_rect2i", "arguments": [ ["const godot_rect2i *", "p_self"], @@ -3758,11 +3758,11 @@ ] }, { - "name": "godot_rect2i_grow_margin", + "name": "godot_rect2i_grow_side", "return_type": "godot_rect2i", "arguments": [ ["const godot_rect2i *", "p_self"], - ["const godot_int", "p_margin"], + ["const godot_int", "p_side"], ["const godot_int", "p_by"] ] }, @@ -5005,7 +5005,7 @@ ] }, { - "name": "godot_string_empty", + "name": "godot_string_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_string *", "p_self"] @@ -6413,7 +6413,7 @@ ] }, { - "name": "godot_vector2_tangent", + "name": "godot_vector2_orthogonal", "return_type": "godot_vector2", "arguments": [ ["const godot_vector2 *", "p_self"] @@ -7946,7 +7946,7 @@ ] }, { - "name": "godot_packed_glyph_array_empty", + "name": "godot_packed_glyph_array_is_empty", "return_type": "godot_bool", "arguments": [ ["const godot_packed_glyph_array *", "p_self"] diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index 719fcbc927..eafe0f43a4 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -65,7 +65,7 @@ void GDNativeLibraryEditor::_update_tree() { continue; } Map<String, NativePlatformConfig>::Element *E = platforms.find(filter_list->get_item_metadata(i)); - if (!text.empty()) { + if (!text.is_empty()) { text += ", "; } text += E->get().name; @@ -91,7 +91,7 @@ void GDNativeLibraryEditor::_update_tree() { bit->add_button(1, get_theme_icon("Folder", "EditorIcons"), BUTTON_SELECT_LIBRARY, false, TTR("Select the dynamic library for this entry")); String file = entry_configs[target].library; - if (!file.empty()) { + if (!file.is_empty()) { bit->add_button(1, get_theme_icon("Clear", "EditorIcons"), BUTTON_CLEAR_LIBRARY, false, TTR("Clear")); } bit->set_text(1, file); @@ -195,7 +195,7 @@ void GDNativeLibraryEditor::_on_item_activated() { void GDNativeLibraryEditor::_on_create_new_entry() { String platform = new_architecture_dialog->get_meta("platform"); String entry = new_architecture_input->get_text().strip_edges(); - if (!entry.empty()) { + if (!entry.is_empty()) { platforms[platform].entries.push_back(entry); _update_tree(); } @@ -248,7 +248,7 @@ void GDNativeLibraryEditor::_translate_to_config_file() { for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) { for (List<String>::Element *it = E->value().entries.front(); it; it = it->next()) { String target = E->key() + "." + it->get(); - if (entry_configs[target].library.empty() && entry_configs[target].dependencies.empty()) { + if (entry_configs[target].library.is_empty() && entry_configs[target].dependencies.is_empty()) { continue; } @@ -327,7 +327,7 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { VBoxContainer *container = memnew(VBoxContainer); add_child(container); - container->set_anchors_and_margins_preset(PRESET_WIDE); + container->set_anchors_and_offsets_preset(PRESET_WIDE); HBoxContainer *hbox = memnew(HBoxContainer); container->add_child(hbox); @@ -381,8 +381,8 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { new_architecture_input = memnew(LineEdit); new_architecture_dialog->add_child(new_architecture_input); // new_architecture_dialog->set_custom_minimum_size(Vector2(300, 80) * EDSCALE); - new_architecture_input->set_anchors_and_margins_preset(PRESET_HCENTER_WIDE, PRESET_MODE_MINSIZE, 5 * EDSCALE); - new_architecture_dialog->get_ok()->connect("pressed", callable_mp(this, &GDNativeLibraryEditor::_on_create_new_entry)); + new_architecture_input->set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE, PRESET_MODE_MINSIZE, 5 * EDSCALE); + new_architecture_dialog->get_ok_button()->connect("pressed", callable_mp(this, &GDNativeLibraryEditor::_on_create_new_entry)); } void GDNativeLibraryEditorPlugin::edit(Object *p_node) { diff --git a/modules/gdnative/include/gdnative/array.h b/modules/gdnative/include/gdnative/array.h index 7a59493b7d..1374a899e5 100644 --- a/modules/gdnative/include/gdnative/array.h +++ b/modules/gdnative/include/gdnative/array.h @@ -87,7 +87,7 @@ void GDAPI godot_array_clear(godot_array *p_self); godot_int GDAPI godot_array_count(const godot_array *p_self, const godot_variant *p_value); -godot_bool GDAPI godot_array_empty(const godot_array *p_self); +godot_bool GDAPI godot_array_is_empty(const godot_array *p_self); void GDAPI godot_array_erase(godot_array *p_self, const godot_variant *p_value); diff --git a/modules/gdnative/include/gdnative/dictionary.h b/modules/gdnative/include/gdnative/dictionary.h index 873efaa9bf..f02e43f173 100644 --- a/modules/gdnative/include/gdnative/dictionary.h +++ b/modules/gdnative/include/gdnative/dictionary.h @@ -67,7 +67,7 @@ godot_dictionary GDAPI godot_dictionary_duplicate(const godot_dictionary *p_self godot_int GDAPI godot_dictionary_size(const godot_dictionary *p_self); -godot_bool GDAPI godot_dictionary_empty(const godot_dictionary *p_self); +godot_bool GDAPI godot_dictionary_is_empty(const godot_dictionary *p_self); void GDAPI godot_dictionary_clear(godot_dictionary *p_self); diff --git a/modules/gdnative/include/gdnative/packed_arrays.h b/modules/gdnative/include/gdnative/packed_arrays.h index ce9579f307..135d4036fc 100644 --- a/modules/gdnative/include/gdnative/packed_arrays.h +++ b/modules/gdnative/include/gdnative/packed_arrays.h @@ -195,7 +195,7 @@ uint8_t GDAPI godot_packed_byte_array_get(const godot_packed_byte_array *p_self, godot_int GDAPI godot_packed_byte_array_size(const godot_packed_byte_array *p_self); -godot_bool GDAPI godot_packed_byte_array_empty(const godot_packed_byte_array *p_self); +godot_bool GDAPI godot_packed_byte_array_is_empty(const godot_packed_byte_array *p_self); void GDAPI godot_packed_byte_array_destroy(godot_packed_byte_array *p_self); @@ -231,7 +231,7 @@ int32_t GDAPI godot_packed_int32_array_get(const godot_packed_int32_array *p_sel godot_int GDAPI godot_packed_int32_array_size(const godot_packed_int32_array *p_self); -godot_bool GDAPI godot_packed_int32_array_empty(const godot_packed_int32_array *p_self); +godot_bool GDAPI godot_packed_int32_array_is_empty(const godot_packed_int32_array *p_self); void GDAPI godot_packed_int32_array_destroy(godot_packed_int32_array *p_self); @@ -267,7 +267,7 @@ int64_t GDAPI godot_packed_int64_array_get(const godot_packed_int64_array *p_sel godot_int GDAPI godot_packed_int64_array_size(const godot_packed_int64_array *p_self); -godot_bool GDAPI godot_packed_int64_array_empty(const godot_packed_int64_array *p_self); +godot_bool GDAPI godot_packed_int64_array_is_empty(const godot_packed_int64_array *p_self); void GDAPI godot_packed_int64_array_destroy(godot_packed_int64_array *p_self); @@ -303,7 +303,7 @@ float GDAPI godot_packed_float32_array_get(const godot_packed_float32_array *p_s godot_int GDAPI godot_packed_float32_array_size(const godot_packed_float32_array *p_self); -godot_bool GDAPI godot_packed_float32_array_empty(const godot_packed_float32_array *p_self); +godot_bool GDAPI godot_packed_float32_array_is_empty(const godot_packed_float32_array *p_self); void GDAPI godot_packed_float32_array_destroy(godot_packed_float32_array *p_self); @@ -339,7 +339,7 @@ double GDAPI godot_packed_float64_array_get(const godot_packed_float64_array *p_ godot_int GDAPI godot_packed_float64_array_size(const godot_packed_float64_array *p_self); -godot_bool GDAPI godot_packed_float64_array_empty(const godot_packed_float64_array *p_self); +godot_bool GDAPI godot_packed_float64_array_is_empty(const godot_packed_float64_array *p_self); void GDAPI godot_packed_float64_array_destroy(godot_packed_float64_array *p_self); @@ -375,7 +375,7 @@ godot_string GDAPI godot_packed_string_array_get(const godot_packed_string_array godot_int GDAPI godot_packed_string_array_size(const godot_packed_string_array *p_self); -godot_bool GDAPI godot_packed_string_array_empty(const godot_packed_string_array *p_self); +godot_bool GDAPI godot_packed_string_array_is_empty(const godot_packed_string_array *p_self); void GDAPI godot_packed_string_array_destroy(godot_packed_string_array *p_self); @@ -411,7 +411,7 @@ godot_vector2 GDAPI godot_packed_vector2_array_get(const godot_packed_vector2_ar godot_int GDAPI godot_packed_vector2_array_size(const godot_packed_vector2_array *p_self); -godot_bool GDAPI godot_packed_vector2_array_empty(const godot_packed_vector2_array *p_self); +godot_bool GDAPI godot_packed_vector2_array_is_empty(const godot_packed_vector2_array *p_self); void GDAPI godot_packed_vector2_array_destroy(godot_packed_vector2_array *p_self); @@ -447,7 +447,7 @@ godot_vector2i GDAPI godot_packed_vector2i_array_get(const godot_packed_vector2i godot_int GDAPI godot_packed_vector2i_array_size(const godot_packed_vector2i_array *p_self); -godot_bool GDAPI godot_packed_vector2i_array_empty(const godot_packed_vector2i_array *p_self); +godot_bool GDAPI godot_packed_vector2i_array_is_empty(const godot_packed_vector2i_array *p_self); void GDAPI godot_packed_vector2i_array_destroy(godot_packed_vector2i_array *p_self); @@ -483,7 +483,7 @@ godot_vector3 GDAPI godot_packed_vector3_array_get(const godot_packed_vector3_ar godot_int GDAPI godot_packed_vector3_array_size(const godot_packed_vector3_array *p_self); -godot_bool GDAPI godot_packed_vector3_array_empty(const godot_packed_vector3_array *p_self); +godot_bool GDAPI godot_packed_vector3_array_is_empty(const godot_packed_vector3_array *p_self); void GDAPI godot_packed_vector3_array_destroy(godot_packed_vector3_array *p_self); @@ -519,7 +519,7 @@ godot_color GDAPI godot_packed_color_array_get(const godot_packed_color_array *p godot_int GDAPI godot_packed_color_array_size(const godot_packed_color_array *p_self); -godot_bool GDAPI godot_packed_color_array_empty(const godot_packed_color_array *p_self); +godot_bool GDAPI godot_packed_color_array_is_empty(const godot_packed_color_array *p_self); void GDAPI godot_packed_color_array_destroy(godot_packed_color_array *p_self); diff --git a/modules/gdnative/include/gdnative/rect2.h b/modules/gdnative/include/gdnative/rect2.h index f317afc9da..5bf546e8c9 100644 --- a/modules/gdnative/include/gdnative/rect2.h +++ b/modules/gdnative/include/gdnative/rect2.h @@ -80,7 +80,7 @@ godot_bool GDAPI godot_rect2_encloses(const godot_rect2 *p_self, const godot_rec godot_bool GDAPI godot_rect2_has_no_area(const godot_rect2 *p_self); -godot_rect2 GDAPI godot_rect2_clip(const godot_rect2 *p_self, const godot_rect2 *p_b); +godot_rect2 GDAPI godot_rect2_intersection(const godot_rect2 *p_self, const godot_rect2 *p_b); godot_rect2 GDAPI godot_rect2_merge(const godot_rect2 *p_self, const godot_rect2 *p_b); @@ -90,7 +90,7 @@ godot_rect2 GDAPI godot_rect2_grow(const godot_rect2 *p_self, const godot_real p godot_rect2 GDAPI godot_rect2_grow_individual(const godot_rect2 *p_self, const godot_real p_left, const godot_real p_top, const godot_real p_right, const godot_real p_bottom); -godot_rect2 GDAPI godot_rect2_grow_margin(const godot_rect2 *p_self, const godot_int p_margin, const godot_real p_by); +godot_rect2 GDAPI godot_rect2_grow_side(const godot_rect2 *p_self, const godot_int p_margin, const godot_real p_by); godot_rect2 GDAPI godot_rect2_abs(const godot_rect2 *p_self); @@ -123,7 +123,7 @@ godot_bool GDAPI godot_rect2i_encloses(const godot_rect2i *p_self, const godot_r godot_bool GDAPI godot_rect2i_has_no_area(const godot_rect2i *p_self); -godot_rect2i GDAPI godot_rect2i_clip(const godot_rect2i *p_self, const godot_rect2i *p_b); +godot_rect2i GDAPI godot_rect2i_intersection(const godot_rect2i *p_self, const godot_rect2i *p_b); godot_rect2i GDAPI godot_rect2i_merge(const godot_rect2i *p_self, const godot_rect2i *p_b); @@ -133,7 +133,7 @@ godot_rect2i GDAPI godot_rect2i_grow(const godot_rect2i *p_self, const godot_int godot_rect2i GDAPI godot_rect2i_grow_individual(const godot_rect2i *p_self, const godot_int p_left, const godot_int p_top, const godot_int p_right, const godot_int p_bottom); -godot_rect2i GDAPI godot_rect2i_grow_margin(const godot_rect2i *p_self, const godot_int p_margin, const godot_int p_by); +godot_rect2i GDAPI godot_rect2i_grow_side(const godot_rect2i *p_self, const godot_int p_margin, const godot_int p_by); godot_rect2i GDAPI godot_rect2i_abs(const godot_rect2i *p_self); diff --git a/modules/gdnative/include/gdnative/string.h b/modules/gdnative/include/gdnative/string.h index 6043351e84..101e119d4d 100644 --- a/modules/gdnative/include/gdnative/string.h +++ b/modules/gdnative/include/gdnative/string.h @@ -256,7 +256,7 @@ godot_string GDAPI godot_string_sha1_text(const godot_string *p_self); godot_packed_byte_array GDAPI godot_string_sha256_buffer(const godot_string *p_self); godot_string GDAPI godot_string_sha256_text(const godot_string *p_self); -godot_bool godot_string_empty(const godot_string *p_self); +godot_bool godot_string_is_empty(const godot_string *p_self); // path functions godot_string GDAPI godot_string_get_base_dir(const godot_string *p_self); diff --git a/modules/gdnative/include/gdnative/vector2.h b/modules/gdnative/include/gdnative/vector2.h index 35b02c5a75..0585ba4a78 100644 --- a/modules/gdnative/include/gdnative/vector2.h +++ b/modules/gdnative/include/gdnative/vector2.h @@ -102,7 +102,7 @@ godot_vector2 GDAPI godot_vector2_move_toward(const godot_vector2 *p_self, const godot_vector2 GDAPI godot_vector2_rotated(const godot_vector2 *p_self, const godot_real p_phi); -godot_vector2 GDAPI godot_vector2_tangent(const godot_vector2 *p_self); +godot_vector2 GDAPI godot_vector2_orthogonal(const godot_vector2 *p_self); godot_vector2 GDAPI godot_vector2_floor(const godot_vector2 *p_self); diff --git a/modules/gdnative/include/pluginscript/godot_pluginscript.h b/modules/gdnative/include/pluginscript/godot_pluginscript.h index 406c3ba663..e4b1fd5eb0 100644 --- a/modules/gdnative/include/pluginscript/godot_pluginscript.h +++ b/modules/gdnative/include/pluginscript/godot_pluginscript.h @@ -72,6 +72,7 @@ typedef struct { godot_string_name name; godot_bool is_tool; godot_string_name base; + godot_string icon_path; // Member lines format: {<string>: <int>} godot_dictionary member_lines; @@ -127,6 +128,7 @@ typedef struct { const char **string_delimiters; // nullptr terminated array godot_bool has_named_classes; godot_bool supports_builtin_mode; + godot_bool can_inherit_from_file; godot_string (*get_template_source_code)(godot_pluginscript_language_data *p_data, const godot_string *p_class_name, const godot_string *p_base_class_name); godot_bool (*validate)(godot_pluginscript_language_data *p_data, const godot_string *p_script, int *r_line_error, int *r_col_error, godot_string *r_test_error, const godot_string *p_path, godot_packed_string_array *r_functions); diff --git a/modules/gdnative/include/text/godot_text.h b/modules/gdnative/include/text/godot_text.h index 2eac6adfb5..200a822e6d 100644 --- a/modules/gdnative/include/text/godot_text.h +++ b/modules/gdnative/include/text/godot_text.h @@ -82,6 +82,9 @@ typedef struct { void (*font_set_antialiased)(void *, godot_rid *, bool); bool (*font_get_antialiased)(void *, godot_rid *); godot_dictionary (*font_get_feature_list)(void *, godot_rid *); + godot_dictionary (*font_get_variation_list)(void *, godot_rid *); + void (*font_set_variation)(void *, godot_rid *, const godot_string *, double); + double (*font_get_variation)(void *, godot_rid *, const godot_string *); void (*font_set_distance_field_hint)(void *, godot_rid *, bool); bool (*font_get_distance_field_hint)(void *, godot_rid *); void (*font_set_hinting)(void *, godot_rid *, godot_int); @@ -215,7 +218,7 @@ godot_glyph GDAPI godot_packed_glyph_array_get(const godot_packed_glyph_array *p godot_int GDAPI godot_packed_glyph_array_size(const godot_packed_glyph_array *p_self); -godot_bool GDAPI godot_packed_glyph_array_empty(const godot_packed_glyph_array *p_self); +godot_bool GDAPI godot_packed_glyph_array_is_empty(const godot_packed_glyph_array *p_self); void GDAPI godot_packed_glyph_array_destroy(godot_packed_glyph_array *p_self); diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index 6f2f9bfea9..c10de8d5a0 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -295,7 +295,7 @@ List<ClassAPI> generate_c_api_classes() { property_api.index = ClassDB::get_property_index(class_name, p->get().name); - if (!property_api.setter.empty() || !property_api.getter.empty()) { + if (!property_api.setter.is_empty() || !property_api.getter.is_empty()) { class_api.properties.push_back(property_api); } } diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index 0939cfd06a..ad2ae6fd4a 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -717,7 +717,7 @@ String NativeScript::get_property_documentation(const StringName &p_path) const } Variant NativeScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - if (lib_path.empty() || class_name.empty() || library.is_null()) { + if (lib_path.is_empty() || class_name.is_empty() || library.is_null()) { r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; return Variant(); } @@ -1800,7 +1800,7 @@ bool NativeScriptLanguage::handles_global_class_type(const String &p_type) const } String NativeScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { - if (!p_path.empty()) { + if (!p_path.is_empty()) { Ref<NativeScript> script = ResourceLoader::load(p_path, "NativeScript"); if (script.is_valid()) { if (r_base_type) { diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp index fc9c4ebd77..93e3181972 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.cpp +++ b/modules/gdnative/pluginscript/pluginscript_language.cpp @@ -142,6 +142,10 @@ bool PluginScriptLanguage::supports_builtin_mode() const { return _desc.supports_builtin_mode; } +bool PluginScriptLanguage::can_inherit_from_file() const { + return _desc.can_inherit_from_file; +} + int PluginScriptLanguage::find_function(const String &p_function, const String &p_code) const { if (_desc.find_function) { return _desc.find_function(_data, (godot_string *)&p_function, (godot_string *)&p_code); @@ -398,6 +402,32 @@ void PluginScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool #endif } +bool PluginScriptLanguage::handles_global_class_type(const String &p_type) const { + return p_type == "PluginScript"; +} + +String PluginScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { + if (!p_path.is_empty()) { + Ref<PluginScript> script = ResourceLoader::load(p_path, "PluginScript"); + if (script.is_valid()) { + if (r_base_type) { + *r_base_type = script->get_instance_base_type(); + } + if (r_icon_path) { + *r_icon_path = script->get_script_class_icon_path(); + } + return script->get_script_class_name(); + } + if (r_base_type) { + *r_base_type = String(); + } + if (r_icon_path) { + *r_icon_path = String(); + } + } + return String(); +} + void PluginScriptLanguage::lock() { _lock.lock(); } diff --git a/modules/gdnative/pluginscript/pluginscript_language.h b/modules/gdnative/pluginscript/pluginscript_language.h index 53e1e3ae9b..7548eba4a0 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.h +++ b/modules/gdnative/pluginscript/pluginscript_language.h @@ -78,7 +78,7 @@ public: virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; - virtual bool can_inherit_from_file() { return true; } + virtual bool can_inherit_from_file() const; virtual int find_function(const String &p_function, const String &p_code) const; virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const; virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint); @@ -122,6 +122,11 @@ public: virtual void frame(); + /* GLOBAL CLASSES */ + + virtual bool handles_global_class_type(const String &p_type) const; + virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const; + void lock(); void unlock(); diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 87c6288806..d69ab2fcb7 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -302,6 +302,7 @@ Error PluginScript::reload(bool p_keep_state) { _data = manifest.data; _name = *(StringName *)&manifest.name; _tool = manifest.is_tool; + _icon_path = *(String *)&manifest.icon_path; Dictionary *members = (Dictionary *)&manifest.member_lines; for (const Variant *key = members->next(); key != nullptr; key = members->next(key)) { diff --git a/modules/gdnative/pluginscript/pluginscript_script.h b/modules/gdnative/pluginscript/pluginscript_script.h index dc1ed6d576..12d93cc407 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.h +++ b/modules/gdnative/pluginscript/pluginscript_script.h @@ -69,6 +69,7 @@ private: String _source; String _path; StringName _name; + String _icon_path; protected: static void _bind_methods(); @@ -84,6 +85,14 @@ protected: virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override; #endif public: + String get_script_class_name() const { + return _name; + } + + String get_script_class_icon_path() const { + return _icon_path; + } + virtual bool can_instance() const override; virtual Ref<Script> get_base_script() const override; //for script inheritance diff --git a/modules/gdnative/tests/test_string.h b/modules/gdnative/tests/test_string.h index 2b1aa5bf28..7eccc74987 100644 --- a/modules/gdnative/tests/test_string.h +++ b/modules/gdnative/tests/test_string.h @@ -259,11 +259,11 @@ TEST_CASE("[GDNative String] Testing for empty string") { godot_string s; godot_string_new_with_latin1_chars(&s, "Mellon"); - CHECK(!godot_string_empty(&s)); + CHECK(!godot_string_is_empty(&s)); godot_string_destroy(&s); godot_string_new_with_latin1_chars(&s, ""); - CHECK(godot_string_empty(&s)); + CHECK(godot_string_is_empty(&s)); godot_string_destroy(&s); } diff --git a/modules/gdnative/text/text_server_gdnative.cpp b/modules/gdnative/text/text_server_gdnative.cpp index 68624260a6..baaa3c0e21 100644 --- a/modules/gdnative/text/text_server_gdnative.cpp +++ b/modules/gdnative/text/text_server_gdnative.cpp @@ -148,6 +148,24 @@ bool TextServerGDNative::font_get_antialiased(RID p_font) const { return interface->font_get_antialiased(data, (godot_rid *)&p_font); } +Dictionary TextServerGDNative::font_get_variation_list(RID p_font) const { + ERR_FAIL_COND_V(interface == nullptr, Dictionary()); + godot_dictionary result = interface->font_get_variation_list(data, (godot_rid *)&p_font); + Dictionary info = *(Dictionary *)&result; + godot_dictionary_destroy(&result); + + return info; +} + +void TextServerGDNative::font_set_variation(RID p_font, const String &p_name, double p_value) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_variation(data, (godot_rid *)&p_font, (godot_string *)&p_name, p_value); +} + +double TextServerGDNative::font_get_variation(RID p_font, const String &p_name) const { + return interface->font_get_variation(data, (godot_rid *)&p_font, (godot_string *)&p_name); +} + void TextServerGDNative::font_set_hinting(RID p_font, TextServer::Hinting p_hinting) { ERR_FAIL_COND(interface == nullptr); interface->font_set_hinting(data, (godot_rid *)&p_font, (godot_int)p_hinting); @@ -821,9 +839,9 @@ godot_int GDAPI godot_packed_glyph_array_size(const godot_packed_glyph_array *p_ return self->size(); } -godot_bool GDAPI godot_packed_glyph_array_empty(const godot_packed_glyph_array *p_self) { +godot_bool GDAPI godot_packed_glyph_array_is_empty(const godot_packed_glyph_array *p_self) { const Vector<TextServer::Glyph> *self = (const Vector<TextServer::Glyph> *)p_self; - return self->empty(); + return self->is_empty(); } void GDAPI godot_packed_glyph_array_destroy(godot_packed_glyph_array *p_self) { diff --git a/modules/gdnative/text/text_server_gdnative.h b/modules/gdnative/text/text_server_gdnative.h index 0196120c00..959302aaf4 100644 --- a/modules/gdnative/text/text_server_gdnative.h +++ b/modules/gdnative/text/text_server_gdnative.h @@ -76,6 +76,10 @@ public: virtual bool font_get_antialiased(RID p_font) const override; virtual Dictionary font_get_feature_list(RID p_font) const override; + virtual Dictionary font_get_variation_list(RID p_font) const override; + + virtual void font_set_variation(RID p_font, const String &p_name, double p_value) override; + virtual double font_get_variation(RID p_font, const String &p_name) const override; virtual void font_set_hinting(RID p_font, Hinting p_hinting) override; virtual Hinting font_get_hinting(RID p_font) const override; diff --git a/modules/gdnative/xr/xr_interface_gdnative.cpp b/modules/gdnative/xr/xr_interface_gdnative.cpp index d03fc33935..d1d575db62 100644 --- a/modules/gdnative/xr/xr_interface_gdnative.cpp +++ b/modules/gdnative/xr/xr_interface_gdnative.cpp @@ -302,12 +302,12 @@ godot_int GDAPI godot_xr_add_controller(char *p_device_name, godot_int p_hand, g ERR_FAIL_NULL_V(input, 0); XRPositionalTracker *new_tracker = memnew(XRPositionalTracker); - new_tracker->set_name(p_device_name); - new_tracker->set_type(XRServer::TRACKER_CONTROLLER); + new_tracker->set_tracker_name(p_device_name); + new_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); if (p_hand == 1) { - new_tracker->set_hand(XRPositionalTracker::TRACKER_LEFT_HAND); + new_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); } else if (p_hand == 2) { - new_tracker->set_hand(XRPositionalTracker::TRACKER_RIGHT_HAND); + new_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); } // also register as joystick... diff --git a/modules/gdnavigation/SCsub b/modules/gdnavigation/SCsub index 877d601c6a..22b5509b32 100644 --- a/modules/gdnavigation/SCsub +++ b/modules/gdnavigation/SCsub @@ -5,6 +5,10 @@ Import("env_modules") env_navigation = env_modules.Clone() +# Thirdparty source files + +thirdparty_obj = [] + # Recast Thirdparty source files if env["builtin_recast"]: thirdparty_dir = "#thirdparty/recastnavigation/Recast/" @@ -23,28 +27,37 @@ if env["builtin_recast"]: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_navigation.Prepend(CPPPATH=[thirdparty_dir + "/Include"]) + env_navigation.Prepend(CPPPATH=[thirdparty_dir + "Include"]) env_thirdparty = env_navigation.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) - + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) # RVO Thirdparty source files if env["builtin_rvo2"]: - thirdparty_dir = "#thirdparty/rvo2" + thirdparty_dir = "#thirdparty/rvo2/" thirdparty_sources = [ - "/src/Agent.cpp", - "/src/KdTree.cpp", + "Agent.cpp", + "KdTree.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_navigation.Prepend(CPPPATH=[thirdparty_dir + "/src"]) + env_navigation.Prepend(CPPPATH=[thirdparty_dir]) env_thirdparty = env_navigation.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + + +env.modules_sources += thirdparty_obj # Godot source files -env_navigation.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_navigation.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/gdnavigation/nav_map.cpp b/modules/gdnavigation/nav_map.cpp index 7919e6a01f..c8c1b422e8 100644 --- a/modules/gdnavigation/nav_map.cpp +++ b/modules/gdnavigation/nav_map.cpp @@ -707,7 +707,7 @@ void NavMap::sync() { } if (regenerate_links) { - map_update_id = map_update_id + 1 % 9999999; + map_update_id = (map_update_id + 1) % 9999999; } if (agents_dirty) { diff --git a/modules/gdnavigation/navigation_mesh_generator.cpp b/modules/gdnavigation/navigation_mesh_generator.cpp index 5ac153d077..88428d5eb4 100644 --- a/modules/gdnavigation/navigation_mesh_generator.cpp +++ b/modules/gdnavigation/navigation_mesh_generator.cpp @@ -151,7 +151,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform p_accumulated_transform, if (Object::cast_to<CSGShape3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_node); Array meshes = csg_shape->get_meshes(); - if (!meshes.empty()) { + if (!meshes.is_empty()) { Ref<Mesh> mesh = meshes[1]; if (mesh.is_valid()) { _add_mesh(mesh, p_accumulated_transform * csg_shape->get_transform(), p_verticies, p_indices); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index eeb66ebfc0..4ed129b3ff 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -763,7 +763,7 @@ <argument index="1" name="exp" type="float"> </argument> <description> - Returns the result of [code]x[/code] raised to the power of [code]y[/code]. + Returns the result of [code]base[/code] raised to the power of [code]exp[/code]. [codeblock] pow(2, 5) # Returns 32.0 [/codeblock] diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 49357f3d2e..e38647eaab 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -42,7 +42,7 @@ private: Color color; String start_key; String end_key; - bool line_only; + bool line_only = false; }; Vector<ColorRegion> color_regions; Map<int, int> color_region_cache; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 4425b59d62..2220341b84 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -425,7 +425,7 @@ void GDScript::_update_doc() { _clear_doc(); doc.script_path = "\"" + get_path().get_slice("://", 1) + "\""; - if (!name.empty()) { + if (!name.is_empty()) { doc.name = name; } else { doc.name = doc.script_path; @@ -793,10 +793,10 @@ Error GDScript::reload(bool p_keep_state) { { String source_path = path; - if (source_path.empty()) { + if (source_path.is_empty()) { source_path = get_path(); } - if (!source_path.empty()) { + if (!source_path.is_empty()) { MutexLock lock(GDScriptCache::singleton->lock); if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this; @@ -812,7 +812,7 @@ Error GDScript::reload(bool p_keep_state) { GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); } // TODO: Show all error messages. - _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); ERR_FAIL_V(ERR_PARSE_ERROR); } @@ -826,7 +826,7 @@ Error GDScript::reload(bool p_keep_state) { const List<GDScriptParser::ParserError>::Element *e = parser.get_errors().front(); while (e != nullptr) { - _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); e = e->next(); } ERR_FAIL_V(ERR_PARSE_ERROR); @@ -846,7 +846,7 @@ Error GDScript::reload(bool p_keep_state) { if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); } - _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT); + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT); ERR_FAIL_V(ERR_COMPILATION_FAILED); } else { return err; @@ -2122,8 +2122,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { w++; } - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - p_words->push_back(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))); + List<StringName> functions; + GDScriptUtilityFunctions::get_function_list(&functions); + + for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { + p_words->push_back(String(E->get())); } } @@ -2148,7 +2151,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b if (err == OK) { const GDScriptParser::ClassNode *c = parser.get_tree(); if (r_icon_path) { - if (c->icon_path.empty() || c->icon_path.is_abs_path()) { + if (c->icon_path.is_empty() || c->icon_path.is_abs_path()) { *r_icon_path = c->icon_path; } else if (c->icon_path.is_rel_path()) { *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); @@ -2160,7 +2163,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b GDScriptParser subparser; while (subclass) { if (subclass->extends_used) { - if (!subclass->extends_path.empty()) { + if (!subclass->extends_path.is_empty()) { if (subclass->extends.size() == 0) { get_global_class_name(subclass->extends_path, r_base_type); subclass = nullptr; @@ -2174,7 +2177,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b } String subsource = subfile->get_as_utf8_string(); - if (subsource.empty()) { + if (subsource.is_empty()) { break; } String subpath = subclass->extends_path; @@ -2371,7 +2374,7 @@ void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<S ERR_FAIL_COND_MSG(!file, "Cannot open file '" + p_path + "'."); String source = file->get_as_utf8_string(); - if (source.empty()) { + if (source.is_empty()) { return; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 3eb260f95f..11c449c5f2 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -72,8 +72,8 @@ class GDScript : public Script { friend class GDScriptFunction; friend class GDScriptAnalyzer; friend class GDScriptCompiler; - friend class GDScriptFunctions; friend class GDScriptLanguage; + friend struct GDScriptUtilityFunctionsDefinitions; Ref<GDScriptNativeClass> native; Ref<GDScript> base; @@ -270,8 +270,8 @@ public: class GDScriptInstance : public ScriptInstance { friend class GDScript; friend class GDScriptFunction; - friend class GDScriptFunctions; friend class GDScriptCompiler; + friend struct GDScriptUtilityFunctionsDefinitions; ObjectID owner_id; Object *owner; @@ -471,7 +471,7 @@ public: virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; virtual bool supports_documentation() const; - virtual bool can_inherit_from_file() { return true; } + virtual bool can_inherit_from_file() const { return true; } virtual int find_function(const String &p_function, const String &p_code) const; virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const; virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 1b76c7f967..a828cad1cf 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -37,6 +37,7 @@ #include "core/os/file_access.h" #include "core/templates/hash_map.h" #include "gdscript.h" +#include "gdscript_utility_functions.h" // TODO: Move this to a central location (maybe core?). static HashMap<StringName, StringName> underscore_map; @@ -59,7 +60,7 @@ static const char *underscore_classes[] = { nullptr, }; static StringName get_real_class_name(const StringName &p_source) { - if (underscore_map.empty()) { + if (underscore_map.is_empty()) { const char **class_name = underscore_classes; while (*class_name != nullptr) { underscore_map[*class_name] = String("_") + *class_name; @@ -72,6 +73,39 @@ static StringName get_real_class_name(const StringName &p_source) { return p_source; } +static MethodInfo info_from_utility_func(const StringName &p_function) { + ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); + + MethodInfo info(p_function); + + if (Variant::has_utility_function_return_value(p_function)) { + info.return_val.type = Variant::get_utility_function_return_type(p_function); + if (info.return_val.type == Variant::NIL) { + info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + } + + if (Variant::is_utility_function_vararg(p_function)) { + info.flags |= METHOD_FLAG_VARARG; + } else { + for (int i = 0; i < Variant::get_utility_function_argument_count(p_function); i++) { + PropertyInfo pi; +#ifdef DEBUG_METHODS_ENABLED + pi.name = Variant::get_utility_function_argument_name(p_function, i); +#else + pi.name = "arg" + itos(i + 1); +#endif + pi.type = Variant::get_utility_function_argument_type(p_function, i); + if (pi.type == Variant::NIL) { + pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + info.arguments.push_back(pi); + } + } + + return info; +} + void GDScriptAnalyzer::cleanup() { underscore_map.clear(); } @@ -175,7 +209,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, int extends_index = 0; - if (!p_class->extends_path.empty()) { + if (!p_class->extends_path.is_empty()) { Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path); if (parser.is_null()) { push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class); @@ -190,7 +224,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, base = parser->get_parser()->head->get_datatype(); } else { - if (p_class->extends.empty()) { + if (p_class->extends.is_empty()) { return ERR_PARSE_ERROR; } const StringName &name = p_class->extends[extends_index++]; @@ -342,7 +376,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.type_source = result.ANNOTATED_EXPLICIT; result.builtin_type = Variant::OBJECT; - if (p_type->type_chain.empty()) { + if (p_type->type_chain.is_empty()) { // void. result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::NIL; @@ -1701,7 +1735,6 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa // Call to name directly. StringName function_name = p_call->function_name; Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); - GDScriptFunctions::Function builtin_function = GDScriptParser::get_builtin_function(function_name); if (builtin_type < Variant::VARIANT_MAX) { // Is a builtin constructor. @@ -1843,10 +1876,52 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } p_call->set_datatype(call_type); return; - } else if (builtin_function < GDScriptFunctions::FUNC_MAX) { - MethodInfo function_info = GDScriptFunctions::get_info(builtin_function); + } else if (GDScriptUtilityFunctions::function_exists(function_name)) { + MethodInfo function_info = GDScriptUtilityFunctions::get_function_info(function_name); + + if (all_is_constant && GDScriptUtilityFunctions::is_function_constant(function_name)) { + // Can call on compilation. + Vector<const Variant *> args; + for (int i = 0; i < p_call->arguments.size(); i++) { + args.push_back(&(p_call->arguments[i]->reduced_value)); + } + + Variant value; + Callable::CallError err; + GDScriptUtilityFunctions::get_function(function_name)(&value, (const Variant **)args.ptr(), args.size(), err); + + switch (err.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { + PropertyInfo wrong_arg = function_info.arguments[err.argument]; + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1, + type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); + } break; + case Callable::CallError::CALL_ERROR_INVALID_METHOD: + push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call); + break; + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call); + break; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + break; // Can't happen in a builtin constructor. + case Callable::CallError::CALL_OK: + p_call->is_constant = true; + p_call->reduced_value = value; + break; + } + } else { + validate_call_arg(function_info, p_call); + } + p_call->set_datatype(type_from_property(function_info.return_val)); + return; + } else if (Variant::has_utility_function(function_name)) { + MethodInfo function_info = info_from_utility_func(function_name); - if (all_is_constant && GDScriptFunctions::is_deterministic(builtin_function)) { + if (all_is_constant && Variant::get_utility_function_type(function_name) == Variant::UTILITY_FUNC_TYPE_MATH) { // Can call on compilation. Vector<const Variant *> args; for (int i = 0; i < p_call->arguments.size(); i++) { @@ -1855,23 +1930,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa Variant value; Callable::CallError err; - GDScriptFunctions::call(builtin_function, (const Variant **)args.ptr(), args.size(), value, err); + Variant::call_utility_function(function_name, &value, (const Variant **)args.ptr(), args.size(), err); switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { PropertyInfo wrong_arg = function_info.arguments[err.argument]; - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", GDScriptFunctions::get_func_name(builtin_function), err.argument + 1, + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1, type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); } break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: - push_error(vformat(R"(Invalid call for function "%s".)", GDScriptFunctions::get_func_name(builtin_function)), p_call); + push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: - push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call); + push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call); break; case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: - push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call); + push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call); break; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: break; // Can't happen in a builtin constructor. @@ -2385,7 +2460,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident // Not found. // Check if it's a builtin function. - if (parser->get_builtin_function(name) < GDScriptFunctions::FUNC_MAX) { + if (GDScriptUtilityFunctions::function_exists(name)) { push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier); } else { push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); @@ -3327,12 +3402,12 @@ Error GDScriptAnalyzer::resolve_inheritance() { Error GDScriptAnalyzer::resolve_interface() { resolve_class_interface(parser->head); - return parser->errors.empty() ? OK : ERR_PARSE_ERROR; + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::resolve_body() { resolve_class_body(parser->head); - return parser->errors.empty() ? OK : ERR_PARSE_ERROR; + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::resolve_program() { @@ -3347,7 +3422,7 @@ Error GDScriptAnalyzer::resolve_program() { } depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED); } - return parser->errors.empty() ? OK : ERR_PARSE_ERROR; + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::analyze() { diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index d89b89c8b9..a5d96077d9 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -283,6 +283,30 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->_constructors_count = 0; } + if (utilities_map.size()) { + function->utilities.resize(utilities_map.size()); + function->_utilities_ptr = function->utilities.ptr(); + function->_utilities_count = utilities_map.size(); + for (const Map<Variant::ValidatedUtilityFunction, int>::Element *E = utilities_map.front(); E; E = E->next()) { + function->utilities.write[E->get()] = E->key(); + } + } else { + function->_utilities_ptr = nullptr; + function->_utilities_count = 0; + } + + if (gds_utilities_map.size()) { + function->gds_utilities.resize(gds_utilities_map.size()); + function->_gds_utilities_ptr = function->gds_utilities.ptr(); + function->_gds_utilities_count = gds_utilities_map.size(); + for (const Map<GDScriptUtilityFunctions::FunctionPtr, int>::Element *E = gds_utilities_map.front(); E; E = E->next()) { + function->gds_utilities.write[E->get()] = E->key(); + } + } else { + function->_gds_utilities_ptr = nullptr; + function->_gds_utilities_count = 0; + } + if (method_bind_map.size()) { function->methods.resize(method_bind_map.size()); function->_methods_ptr = function->methods.ptrw(); @@ -404,7 +428,7 @@ void GDScriptByteCodeGenerator::write_end_and(const Address &p_target) { patch_jump(logic_op_jump_pos2.back()->get()); logic_op_jump_pos1.pop_back(); logic_op_jump_pos2.pop_back(); - append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 0); + append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); append(p_target); } @@ -429,7 +453,7 @@ void GDScriptByteCodeGenerator::write_end_or(const Address &p_target) { // Jump away from the success condition. append(GDScriptFunction::OPCODE_JUMP, 0); append(opcodes.size() + 3); - // Here it means one of operands is false. + // Here it means one of operands is true. patch_jump(logic_op_jump_pos1.back()->get()); patch_jump(logic_op_jump_pos2.back()->get()); logic_op_jump_pos1.pop_back(); @@ -704,8 +728,8 @@ void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const append(p_function_name); } -void GDScriptByteCodeGenerator::write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CALL_BUILT_IN, 1 + p_arguments.size()); +void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CALL_GDSCRIPT_UTILITY, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } @@ -714,6 +738,41 @@ void GDScriptByteCodeGenerator::write_call_builtin(const Address &p_target, GDSc append(p_function); } +void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) { + bool is_validated = true; + if (Variant::is_utility_function_vararg(p_function)) { + is_validated = true; // Vararg works fine with any argument, since they can be any type. + } else if (p_arguments.size() == Variant::get_utility_function_argument_count(p_function)) { + bool all_types_exact = true; + for (int i = 0; i < p_arguments.size(); i++) { + if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_utility_function_argument_type(p_function, i))) { + all_types_exact = false; + break; + } + } + + is_validated = all_types_exact; + } + + if (is_validated) { + append(GDScriptFunction::OPCODE_CALL_UTILITY_VALIDATED, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(Variant::get_validated_utility_function(p_function)); + } else { + append(GDScriptFunction::OPCODE_CALL_UTILITY, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(p_function); + } +} + void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { bool is_validated = false; diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 5cbd12a0ba..21576b08a4 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -34,6 +34,7 @@ #include "gdscript_codegen.h" #include "gdscript_function.h" +#include "gdscript_utility_functions.h" class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { bool ended = false; @@ -76,6 +77,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { Map<Variant::ValidatedIndexedGetter, int> indexed_getters_map; Map<Variant::ValidatedBuiltInMethod, int> builtin_method_map; Map<Variant::ValidatedConstructor, int> constructors_map; + Map<Variant::ValidatedUtilityFunction, int> utilities_map; + Map<GDScriptUtilityFunctions::FunctionPtr, int> gds_utilities_map; Map<MethodBind *, int> method_bind_map; // Lists since these can be nested. @@ -241,6 +244,24 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return pos; } + int get_utility_pos(const Variant::ValidatedUtilityFunction p_utility) { + if (utilities_map.has(p_utility)) { + return utilities_map[p_utility]; + } + int pos = utilities_map.size(); + utilities_map[p_utility] = pos; + return pos; + } + + int get_gds_utility_pos(const GDScriptUtilityFunctions::FunctionPtr p_gds_utility) { + if (gds_utilities_map.has(p_gds_utility)) { + return gds_utilities_map[p_gds_utility]; + } + int pos = gds_utilities_map.size(); + gds_utilities_map[p_gds_utility] = pos; + return pos; + } + int get_method_bind_pos(MethodBind *p_method) { if (method_bind_map.has(p_method)) { return method_bind_map[p_method]; @@ -346,6 +367,14 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { opcodes.push_back(get_constructor_pos(p_constructor)); } + void append(const Variant::ValidatedUtilityFunction p_utility) { + opcodes.push_back(get_utility_pos(p_utility)); + } + + void append(const GDScriptUtilityFunctions::FunctionPtr p_gds_utility) { + opcodes.push_back(get_gds_utility_pos(p_gds_utility)); + } + void append(MethodBind *p_method) { opcodes.push_back(get_method_bind_pos(p_method)); } @@ -406,7 +435,8 @@ public: virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; - virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) override; + virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override; + virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) override; virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 559f9b8406..e776007bd7 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -35,7 +35,7 @@ #include "core/string/string_name.h" #include "core/variant/variant.h" #include "gdscript_function.h" -#include "gdscript_functions.h" +#include "gdscript_utility_functions.h" class GDScriptCodeGenerator { public: @@ -127,7 +127,8 @@ public: virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; - virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) = 0; + virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) = 0; + virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) = 0; virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index e3f058886f..24b24ad534 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -33,6 +33,7 @@ #include "gdscript.h" #include "gdscript_byte_codegen.h" #include "gdscript_cache.h" +#include "gdscript_utility_functions.h" bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { if (codegen.function_node && codegen.function_node->is_static) { @@ -106,7 +107,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D // Locate class by constructing the path to it and following that path GDScriptParser::ClassNode *class_type = p_datatype.class_type; if (class_type) { - if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.empty() && class_type->fqcn.begins_with(main_script->name))) { + if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.is_empty() && class_type->fqcn.begins_with(main_script->name))) { // Local class. List<StringName> names; while (class_type->outer) { @@ -456,15 +457,17 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code arguments.push_back(arg); } - if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != Variant::VARIANT_MAX) { + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) { // Construct a built-in type. Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); gen->write_construct(result, vtype, arguments); - } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != GDScriptFunctions::FUNC_MAX) { - // Built-in function. - GDScriptFunctions::Function func = GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - gen->write_call_builtin(result, func, arguments); + } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) { + // Variant utility function. + gen->write_call_utility(result, call->function_name, arguments); + } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptUtilityFunctions::function_exists(call->function_name)) { + // GDScript utility function. + gen->write_call_gdscript_utility(result, GDScriptUtilityFunctions::get_function(call->function_name), arguments); } else { // Regular function. const GDScriptParser::ExpressionNode *callee = call->callee; @@ -1135,7 +1138,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c // Evaluate expression type. Vector<GDScriptCodeGenerator::Address> typeof_args; typeof_args.push_back(expr_addr); - codegen.generator->write_call_builtin(result_addr, GDScriptFunctions::TYPE_OF, typeof_args); + codegen.generator->write_call_utility(result_addr, "typeof", typeof_args); // Check type equality. codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, result_addr); @@ -1199,7 +1202,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); Vector<GDScriptCodeGenerator::Address> len_args; len_args.push_back(p_value_addr); - codegen.generator->write_call_builtin(value_length_addr, GDScriptFunctions::LEN, len_args); + codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), len_args); // Test length compatibility. temp_type.builtin_type = Variant::BOOL; @@ -1253,7 +1256,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c // Also get type of element. Vector<GDScriptCodeGenerator::Address> typeof_args; typeof_args.push_back(element_addr); - codegen.generator->write_call_builtin(element_type_addr, GDScriptFunctions::TYPE_OF, typeof_args); + codegen.generator->write_call_utility(element_type_addr, "typeof", typeof_args); // Try the pattern inside the element. test_addr = _parse_match_pattern(codegen, r_error, p_pattern->array[i], element_addr, element_type_addr, p_previous_test, false, true); @@ -1298,7 +1301,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); Vector<GDScriptCodeGenerator::Address> func_args; func_args.push_back(p_value_addr); - codegen.generator->write_call_builtin(value_length_addr, GDScriptFunctions::LEN, func_args); + codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), func_args); // Test length compatibility. temp_type.builtin_type = Variant::BOOL; @@ -1367,7 +1370,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c // Also get type of value. func_args.clear(); func_args.push_back(element_addr); - codegen.generator->write_call_builtin(element_type_addr, GDScriptFunctions::TYPE_OF, func_args); + codegen.generator->write_call_utility(element_type_addr, "typeof", func_args); // Try the pattern inside the value. test_addr = _parse_match_pattern(codegen, r_error, element.value_pattern, element_addr, element_type_addr, test_addr, false, true); @@ -1500,7 +1503,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui Vector<GDScriptCodeGenerator::Address> typeof_args; typeof_args.push_back(value); - gen->write_call_builtin(type, GDScriptFunctions::TYPE_OF, typeof_args); + gen->write_call_utility(type, "typeof", typeof_args); // Now we can actually start testing. // For each branch. @@ -2228,7 +2231,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } p_script->_signals[name] = parameters_names; #ifdef TOOLS_ENABLED - if (!signal->doc_description.empty()) { + if (!signal->doc_description.is_empty()) { p_script->doc_signals[name] = signal->doc_description; } #endif diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 92a44c57f8..5938cfd7b2 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -34,7 +34,6 @@ #include "core/string/string_builder.h" #include "gdscript.h" -#include "gdscript_functions.h" static String _get_variant_string(const Variant &p_variant) { String txt; @@ -324,11 +323,8 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 4; } break; case OPCODE_ASSIGN_TYPED_NATIVE: { - Variant class_name = _constants_ptr[_code_ptr[ip + 3]]; - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(class_name.operator Object *()); - text += "assign typed native ("; - text += nc->get_name().operator String(); + text += DADDR(3); text += ") "; text += DADDR(1); text += " = "; @@ -607,13 +603,49 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 5 + argc; } break; - case OPCODE_CALL_BUILT_IN: { - text += "call-built-in "; + case OPCODE_CALL_UTILITY: { + text += "call-utility "; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(1 + argc) + " = "; + + text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]]; + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CALL_UTILITY_VALIDATED: { + text += "call-utility "; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(1 + argc) + " = "; + + text += "<unkown function>"; + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CALL_GDSCRIPT_UTILITY: { + text += "call-gscript-utility "; int argc = _code_ptr[ip + 1 + instr_var_args]; text += DADDR(1 + argc) + " = "; - text += GDScriptFunctions::get_func_name(GDScriptFunctions::Function(_code_ptr[ip + 2 + instr_var_args])); + text += "<unknown function>"; text += "("; for (int i = 0; i < argc; i++) { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index af9673a9b8..c4d5982622 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -37,6 +37,7 @@ #include "gdscript_compiler.h" #include "gdscript_parser.h" #include "gdscript_tokenizer.h" +#include "gdscript_utility_functions.h" #ifdef TOOLS_ENABLED #include "core/config/project_settings.h" @@ -122,10 +123,10 @@ static void get_function_names_recursively(const GDScriptParser::ClassNode *p_cl for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::FUNCTION) { const GDScriptParser::FunctionNode *function = p_class->members[i].function; - r_funcs[function->start_line] = p_prefix.empty() ? String(function->identifier->name) : p_prefix + "." + String(function->identifier->name); + r_funcs[function->start_line] = p_prefix.is_empty() ? String(function->identifier->name) : p_prefix + "." + String(function->identifier->name); } else if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { String new_prefix = p_class->members[i].m_class->identifier->name; - get_function_names_recursively(p_class->members[i].m_class, p_prefix.empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs); + get_function_names_recursively(p_class->members[i].m_class, p_prefix.is_empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs); } } } @@ -411,11 +412,14 @@ void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) con } void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - p_functions->push_back(GDScriptFunctions::get_info(GDScriptFunctions::Function(i))); + List<StringName> functions; + GDScriptUtilityFunctions::get_function_list(&functions); + + for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { + p_functions->push_back(GDScriptUtilityFunctions::get_function_info(E->get())); } - //not really "functions", but.. + // Not really "functions", but show in documentation. { MethodInfo mi; mi.name = "preload"; @@ -472,7 +476,7 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na s += p_args[i].get_slice(":", 0); if (th) { String type = p_args[i].get_slice(":", 1); - if (!type.empty() && type != "var") { + if (!type.is_empty() && type != "var") { s += ": " + type; } } @@ -1030,9 +1034,12 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1); } - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - MethodInfo function = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); - ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION); + List<StringName> functions; + GDScriptUtilityFunctions::get_function_list(&functions); + + for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { + MethodInfo function = GDScriptUtilityFunctions::get_function_info(E->get()); + ScriptCodeCompletionOption option(String(E->get()), ScriptCodeCompletionOption::KIND_FUNCTION); if (function.arguments.size() || (function.flags & METHOD_FLAG_VARARG)) { option.insert_text += "("; } else { @@ -1288,8 +1295,8 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name); found = true; break; - } else if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) { - MethodInfo mi = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name)); + } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) { + MethodInfo mi = GDScriptUtilityFunctions::get_function_info(call->function_name); r_type = _type_from_property(mi.return_val); found = true; break; @@ -2342,8 +2349,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c GDScriptCompletionIdentifier connect_base; - if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) { - MethodInfo info = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name)); + if (GDScriptUtilityFunctions::function_exists(call->function_name)) { + MethodInfo info = GDScriptUtilityFunctions::get_function_info(call->function_name); r_arghint = _make_arguments_hint(info, p_argidx); return; } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { @@ -2504,7 +2511,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } - if (!type.enumeration.empty()) { + if (!type.enumeration.is_empty()) { _find_enumeration_candidates(completion_context, type.enumeration, options); r_forced = options.size() > 0; } else { @@ -2971,13 +2978,11 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - if (GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i)) == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = "@GDScript"; - r_result.class_member = p_symbol; - return OK; - } + if (GDScriptUtilityFunctions::function_exists(p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name = "@GDScript"; + r_result.class_member = p_symbol; + return OK; } if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 32372439c5..6d49022d3c 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -114,7 +114,7 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String ERR_CONTINUE(!sdmap.has(sd.identifier)); sdmap[sd.identifier].pos.pop_back(); - if (sdmap[sd.identifier].pos.empty()) { + if (sdmap[sd.identifier].pos.is_empty()) { sdmap.erase(sd.identifier); } } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 7bc20672d5..b669870b51 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -38,6 +38,7 @@ #include "core/templates/pair.h" #include "core/templates/self_list.h" #include "core/variant/variant.h" +#include "gdscript_utility_functions.h" class GDScriptInstance; class GDScript; @@ -190,7 +191,9 @@ public: OPCODE_CALL, OPCODE_CALL_RETURN, OPCODE_CALL_ASYNC, - OPCODE_CALL_BUILT_IN, + OPCODE_CALL_UTILITY, + OPCODE_CALL_UTILITY_VALIDATED, + OPCODE_CALL_GDSCRIPT_UTILITY, OPCODE_CALL_BUILTIN_TYPE_VALIDATED, OPCODE_CALL_SELF_BASE, OPCODE_CALL_METHOD_BIND, @@ -344,6 +347,10 @@ private: const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr; int _constructors_count = 0; const Variant::ValidatedConstructor *_constructors_ptr = nullptr; + int _utilities_count = 0; + const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr; + int _gds_utilities_count = 0; + const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr; int _methods_count = 0; MethodBind **_methods_ptr = nullptr; const int *_code_ptr = nullptr; @@ -372,6 +379,8 @@ private: Vector<Variant::ValidatedIndexedGetter> indexed_getters; Vector<Variant::ValidatedBuiltInMethod> builtin_methods; Vector<Variant::ValidatedConstructor> constructors; + Vector<Variant::ValidatedUtilityFunction> utilities; + Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities; Vector<MethodBind *> methods; Vector<int> code; Vector<GDScriptDataType> argument_types; diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp deleted file mode 100644 index 3a7c1a8676..0000000000 --- a/modules/gdscript/gdscript_functions.cpp +++ /dev/null @@ -1,1942 +0,0 @@ -/*************************************************************************/ -/* gdscript_functions.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 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 "gdscript_functions.h" - -#include "core/io/json.h" -#include "core/io/marshalls.h" -#include "core/math/math_funcs.h" -#include "core/object/class_db.h" -#include "core/object/reference.h" -#include "core/os/os.h" -#include "core/variant/variant_parser.h" -#include "gdscript.h" - -const char *GDScriptFunctions::get_func_name(Function p_func) { - ERR_FAIL_INDEX_V(p_func, FUNC_MAX, ""); - - static const char *_names[FUNC_MAX] = { - "sin", - "cos", - "tan", - "sinh", - "cosh", - "tanh", - "asin", - "acos", - "atan", - "atan2", - "sqrt", - "fmod", - "fposmod", - "posmod", - "floor", - "ceil", - "round", - "abs", - "sign", - "pow", - "log", - "exp", - "is_nan", - "is_inf", - "is_equal_approx", - "is_zero_approx", - "ease", - "step_decimals", - "stepify", - "lerp", - "lerp_angle", - "inverse_lerp", - "range_lerp", - "smoothstep", - "move_toward", - "dectime", - "randomize", - "randi", - "randf", - "randf_range", - "randi_range", - "seed", - "rand_seed", - "deg2rad", - "rad2deg", - "linear2db", - "db2linear", - "polar2cartesian", - "cartesian2polar", - "wrapi", - "wrapf", - "max", - "min", - "clamp", - "nearest_po2", - "weakref", - "convert", - "typeof", - "type_exists", - "char", - "ord", - "str", - "print", - "printt", - "prints", - "printerr", - "printraw", - "print_debug", - "push_error", - "push_warning", - "var2str", - "str2var", - "var2bytes", - "bytes2var", - "range", - "load", - "inst2dict", - "dict2inst", - "validate_json", - "parse_json", - "to_json", - "hash", - "Color8", - "ColorN", - "print_stack", - "get_stack", - "instance_from_id", - "len", - "is_instance_valid", - }; - - return _names[p_func]; -} - -void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_count, Variant &r_ret, Callable::CallError &r_error) { - r_error.error = Callable::CallError::CALL_OK; -#ifdef DEBUG_ENABLED - -#define VALIDATE_ARG_COUNT(m_count) \ - if (p_arg_count < m_count) { \ - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ - r_error.argument = m_count; \ - r_error.expected = m_count; \ - r_ret = Variant(); \ - return; \ - } \ - if (p_arg_count > m_count) { \ - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \ - r_error.argument = m_count; \ - r_error.expected = m_count; \ - r_ret = Variant(); \ - return; \ - } - -#define VALIDATE_ARG_NUM(m_arg) \ - if (!p_args[m_arg]->is_num()) { \ - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \ - r_error.argument = m_arg; \ - r_error.expected = Variant::FLOAT; \ - r_ret = Variant(); \ - return; \ - } - -#else - -#define VALIDATE_ARG_COUNT(m_count) -#define VALIDATE_ARG_NUM(m_arg) -#endif - - //using a switch, so the compiler generates a jumptable - - switch (p_func) { - case MATH_SIN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::sin((double)*p_args[0]); - } break; - case MATH_COS: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::cos((double)*p_args[0]); - } break; - case MATH_TAN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::tan((double)*p_args[0]); - } break; - case MATH_SINH: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::sinh((double)*p_args[0]); - } break; - case MATH_COSH: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::cosh((double)*p_args[0]); - } break; - case MATH_TANH: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::tanh((double)*p_args[0]); - } break; - case MATH_ASIN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::asin((double)*p_args[0]); - } break; - case MATH_ACOS: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::acos((double)*p_args[0]); - } break; - case MATH_ATAN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::atan((double)*p_args[0]); - } break; - case MATH_ATAN2: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::atan2((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_SQRT: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::sqrt((double)*p_args[0]); - } break; - case MATH_FMOD: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::fmod((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_FPOSMOD: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::fposmod((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_POSMOD: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::posmod((int)*p_args[0], (int)*p_args[1]); - } break; - case MATH_FLOOR: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::floor((double)*p_args[0]); - } break; - case MATH_CEIL: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::ceil((double)*p_args[0]); - } break; - case MATH_ROUND: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::round((double)*p_args[0]); - } break; - case MATH_ABS: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() == Variant::INT) { - int64_t i = *p_args[0]; - r_ret = ABS(i); - } else if (p_args[0]->get_type() == Variant::FLOAT) { - double r = *p_args[0]; - r_ret = Math::abs(r); - } else { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::FLOAT; - r_ret = Variant(); - } - } break; - case MATH_SIGN: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() == Variant::INT) { - int64_t i = *p_args[0]; - r_ret = i < 0 ? -1 : (i > 0 ? +1 : 0); - } else if (p_args[0]->get_type() == Variant::FLOAT) { - double r = *p_args[0]; - r_ret = r < 0.0 ? -1.0 : (r > 0.0 ? +1.0 : 0.0); - } else { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::FLOAT; - r_ret = Variant(); - } - } break; - case MATH_POW: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::pow((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_LOG: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::log((double)*p_args[0]); - } break; - case MATH_EXP: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::exp((double)*p_args[0]); - } break; - case MATH_ISNAN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::is_nan((double)*p_args[0]); - } break; - case MATH_ISINF: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::is_inf((double)*p_args[0]); - } break; - case MATH_ISEQUALAPPROX: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::is_equal_approx((real_t)*p_args[0], (real_t)*p_args[1]); - } break; - case MATH_ISZEROAPPROX: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::is_zero_approx((real_t)*p_args[0]); - } break; - case MATH_EASE: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::ease((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_STEP_DECIMALS: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::step_decimals((double)*p_args[0]); - } break; - case MATH_STEPIFY: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::stepify((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_LERP: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(2); - const double t = (double)*p_args[2]; - switch (p_args[0]->get_type() == p_args[1]->get_type() ? p_args[0]->get_type() : Variant::FLOAT) { - case Variant::VECTOR2: { - r_ret = ((Vector2)*p_args[0]).lerp((Vector2)*p_args[1], t); - } break; - case Variant::VECTOR3: { - r_ret = (p_args[0]->operator Vector3()).lerp(p_args[1]->operator Vector3(), t); - } break; - case Variant::COLOR: { - r_ret = ((Color)*p_args[0]).lerp((Color)*p_args[1], t); - } break; - default: { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::lerp((double)*p_args[0], (double)*p_args[1], t); - } break; - } - } break; - case MATH_LERP_ANGLE: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::lerp_angle((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_INVERSE_LERP: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::inverse_lerp((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_RANGE_LERP: { - VALIDATE_ARG_COUNT(5); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - VALIDATE_ARG_NUM(3); - VALIDATE_ARG_NUM(4); - r_ret = Math::range_lerp((double)*p_args[0], (double)*p_args[1], (double)*p_args[2], (double)*p_args[3], (double)*p_args[4]); - } break; - case MATH_SMOOTHSTEP: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::smoothstep((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_MOVE_TOWARD: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::move_toward((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_DECTIME: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::dectime((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_RANDOMIZE: { - VALIDATE_ARG_COUNT(0); - Math::randomize(); - r_ret = Variant(); - } break; - case MATH_RANDI: { - VALIDATE_ARG_COUNT(0); - r_ret = Math::rand(); - } break; - case MATH_RANDF: { - VALIDATE_ARG_COUNT(0); - r_ret = Math::randf(); - } break; - case MATH_RANDF_RANGE: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::random((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_RANDI_RANGE: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::random((int)*p_args[0], (int)*p_args[1]); - } break; - case MATH_SEED: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - uint64_t seed = *p_args[0]; - Math::seed(seed); - r_ret = Variant(); - } break; - case MATH_RANDSEED: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - uint64_t seed = *p_args[0]; - int ret = Math::rand_from_seed(&seed); - Array reta; - reta.push_back(ret); - reta.push_back(seed); - r_ret = reta; - - } break; - case MATH_DEG2RAD: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::deg2rad((double)*p_args[0]); - } break; - case MATH_RAD2DEG: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::rad2deg((double)*p_args[0]); - } break; - case MATH_LINEAR2DB: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::linear2db((double)*p_args[0]); - } break; - case MATH_DB2LINEAR: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::db2linear((double)*p_args[0]); - } break; - case MATH_POLAR2CARTESIAN: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - double r = *p_args[0]; - double th = *p_args[1]; - r_ret = Vector2(r * Math::cos(th), r * Math::sin(th)); - } break; - case MATH_CARTESIAN2POLAR: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - double x = *p_args[0]; - double y = *p_args[1]; - r_ret = Vector2(Math::sqrt(x * x + y * y), Math::atan2(y, x)); - } break; - case MATH_WRAP: { - VALIDATE_ARG_COUNT(3); - r_ret = Math::wrapi((int64_t)*p_args[0], (int64_t)*p_args[1], (int64_t)*p_args[2]); - } break; - case MATH_WRAPF: { - VALIDATE_ARG_COUNT(3); - r_ret = Math::wrapf((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case LOGIC_MAX: { - VALIDATE_ARG_COUNT(2); - if (p_args[0]->get_type() == Variant::INT && p_args[1]->get_type() == Variant::INT) { - int64_t a = *p_args[0]; - int64_t b = *p_args[1]; - r_ret = MAX(a, b); - } else { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - - double a = *p_args[0]; - double b = *p_args[1]; - - r_ret = MAX(a, b); - } - - } break; - case LOGIC_MIN: { - VALIDATE_ARG_COUNT(2); - if (p_args[0]->get_type() == Variant::INT && p_args[1]->get_type() == Variant::INT) { - int64_t a = *p_args[0]; - int64_t b = *p_args[1]; - r_ret = MIN(a, b); - } else { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - - double a = *p_args[0]; - double b = *p_args[1]; - - r_ret = MIN(a, b); - } - } break; - case LOGIC_CLAMP: { - VALIDATE_ARG_COUNT(3); - if (p_args[0]->get_type() == Variant::INT && p_args[1]->get_type() == Variant::INT && p_args[2]->get_type() == Variant::INT) { - int64_t a = *p_args[0]; - int64_t b = *p_args[1]; - int64_t c = *p_args[2]; - r_ret = CLAMP(a, b, c); - } else { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - - double a = *p_args[0]; - double b = *p_args[1]; - double c = *p_args[2]; - - r_ret = CLAMP(a, b, c); - } - } break; - case LOGIC_NEAREST_PO2: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - int64_t num = *p_args[0]; - r_ret = next_power_of_2(num); - } break; - case OBJ_WEAKREF: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() == Variant::OBJECT) { - if (p_args[0]->is_ref()) { - Ref<WeakRef> wref = memnew(WeakRef); - REF r = *p_args[0]; - if (r.is_valid()) { - wref->set_ref(r); - } - r_ret = wref; - } else { - Ref<WeakRef> wref = memnew(WeakRef); - Object *obj = *p_args[0]; - if (obj) { - wref->set_obj(obj); - } - r_ret = wref; - } - } else if (p_args[0]->get_type() == Variant::NIL) { - Ref<WeakRef> wref = memnew(WeakRef); - r_ret = wref; - } else { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = Variant(); - return; - } - } break; - case TYPE_CONVERT: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(1); - int type = *p_args[1]; - if (type < 0 || type >= Variant::VARIANT_MAX) { - r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants."); - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::INT; - return; - - } else { - Variant::construct(Variant::Type(type), r_ret, p_args, 1, r_error); - } - } break; - case TYPE_OF: { - VALIDATE_ARG_COUNT(1); - r_ret = p_args[0]->get_type(); - - } break; - case TYPE_EXISTS: { - VALIDATE_ARG_COUNT(1); - r_ret = ClassDB::class_exists(*p_args[0]); - - } break; - case TEXT_CHAR: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - char32_t result[2] = { *p_args[0], 0 }; - r_ret = String(result); - } break; - case TEXT_ORD: { - VALIDATE_ARG_COUNT(1); - - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = Variant(); - return; - } - - String str = p_args[0]->operator String(); - - if (str.length() != 1) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = RTR("Expected a string of length 1 (a character)."); - return; - } - - r_ret = str.get(0); - - } break; - case TEXT_STR: { - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_ret = Variant(); - - return; - } - String str; - for (int i = 0; i < p_arg_count; i++) { - String os = p_args[i]->operator String(); - - if (i == 0) { - str = os; - } else { - str += os; - } - } - - r_ret = str; - - } break; - case TEXT_PRINT: { - String str; - for (int i = 0; i < p_arg_count; i++) { - str += p_args[i]->operator String(); - } - - print_line(str); - r_ret = Variant(); - - } break; - case TEXT_PRINT_TABBED: { - String str; - for (int i = 0; i < p_arg_count; i++) { - if (i) { - str += "\t"; - } - str += p_args[i]->operator String(); - } - - print_line(str); - r_ret = Variant(); - - } break; - case TEXT_PRINT_SPACED: { - String str; - for (int i = 0; i < p_arg_count; i++) { - if (i) { - str += " "; - } - str += p_args[i]->operator String(); - } - - print_line(str); - r_ret = Variant(); - - } break; - - case TEXT_PRINTERR: { - String str; - for (int i = 0; i < p_arg_count; i++) { - str += p_args[i]->operator String(); - } - - print_error(str); - r_ret = Variant(); - - } break; - case TEXT_PRINTRAW: { - String str; - for (int i = 0; i < p_arg_count; i++) { - str += p_args[i]->operator String(); - } - - OS::get_singleton()->print("%s", str.utf8().get_data()); - r_ret = Variant(); - - } break; - case TEXT_PRINT_DEBUG: { - String str; - for (int i = 0; i < p_arg_count; i++) { - str += p_args[i]->operator String(); - } - - ScriptLanguage *script = GDScriptLanguage::get_singleton(); - if (script->debug_get_stack_level_count() > 0) { - str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; - } - - print_line(str); - r_ret = Variant(); - } break; - case PUSH_ERROR: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = Variant(); - break; - } - - String message = *p_args[0]; - ERR_PRINT(message); - r_ret = Variant(); - } break; - case PUSH_WARNING: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = Variant(); - break; - } - - String message = *p_args[0]; - WARN_PRINT(message); - r_ret = Variant(); - } break; - case VAR_TO_STR: { - VALIDATE_ARG_COUNT(1); - String vars; - VariantWriter::write_to_string(*p_args[0], vars); - r_ret = vars; - } break; - case STR_TO_VAR: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = Variant(); - return; - } - r_ret = *p_args[0]; - - VariantParser::StreamString ss; - ss.s = *p_args[0]; - - String errs; - int line; - (void)VariantParser::parse(&ss, r_ret, errs, line); - } break; - case VAR_TO_BYTES: { - bool full_objects = false; - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_ret = Variant(); - return; - } else if (p_arg_count > 2) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 2; - r_ret = Variant(); - } else if (p_arg_count == 2) { - if (p_args[1]->get_type() != Variant::BOOL) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 1; - r_error.expected = Variant::BOOL; - r_ret = Variant(); - return; - } - full_objects = *p_args[1]; - } - - PackedByteArray barr; - int len; - Error err = encode_variant(*p_args[0], nullptr, len, full_objects); - if (err) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::NIL; - r_ret = "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."; - return; - } - - barr.resize(len); - { - uint8_t *w = barr.ptrw(); - encode_variant(*p_args[0], w, len, full_objects); - } - r_ret = barr; - } break; - case BYTES_TO_VAR: { - bool allow_objects = false; - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_ret = Variant(); - return; - } else if (p_arg_count > 2) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 2; - r_ret = Variant(); - } else if (p_arg_count == 2) { - if (p_args[1]->get_type() != Variant::BOOL) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 1; - r_error.expected = Variant::BOOL; - r_ret = Variant(); - return; - } - allow_objects = *p_args[1]; - } - - if (p_args[0]->get_type() != Variant::PACKED_BYTE_ARRAY) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 1; - r_error.expected = Variant::PACKED_BYTE_ARRAY; - r_ret = Variant(); - return; - } - - PackedByteArray varr = *p_args[0]; - Variant ret; - { - const uint8_t *r = varr.ptr(); - Error err = decode_variant(ret, r, varr.size(), nullptr, allow_objects); - if (err != OK) { - r_ret = RTR("Not enough bytes for decoding bytes, or invalid format."); - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::PACKED_BYTE_ARRAY; - return; - } - } - - r_ret = ret; - - } break; - case GEN_RANGE: { - switch (p_arg_count) { - case 0: { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_error.expected = 1; - r_ret = Variant(); - - } break; - case 1: { - VALIDATE_ARG_NUM(0); - int count = *p_args[0]; - Array arr; - if (count <= 0) { - r_ret = arr; - return; - } - Error err = arr.resize(count); - if (err != OK) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - r_ret = Variant(); - return; - } - - for (int i = 0; i < count; i++) { - arr[i] = i; - } - - r_ret = arr; - } break; - case 2: { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - - int from = *p_args[0]; - int to = *p_args[1]; - - Array arr; - if (from >= to) { - r_ret = arr; - return; - } - Error err = arr.resize(to - from); - if (err != OK) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - r_ret = Variant(); - return; - } - for (int i = from; i < to; i++) { - arr[i - from] = i; - } - r_ret = arr; - } break; - case 3: { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - - int from = *p_args[0]; - int to = *p_args[1]; - int incr = *p_args[2]; - if (incr == 0) { - r_ret = RTR("Step argument is zero!"); - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return; - } - - Array arr; - if (from >= to && incr > 0) { - r_ret = arr; - return; - } - if (from <= to && incr < 0) { - r_ret = arr; - return; - } - - //calculate how many - int count = 0; - if (incr > 0) { - count = ((to - from - 1) / incr) + 1; - } else { - count = ((from - to - 1) / -incr) + 1; - } - - Error err = arr.resize(count); - - if (err != OK) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - r_ret = Variant(); - return; - } - - if (incr > 0) { - int idx = 0; - for (int i = from; i < to; i += incr) { - arr[idx++] = i; - } - } else { - int idx = 0; - for (int i = from; i > to; i += incr) { - arr[idx++] = i; - } - } - - r_ret = arr; - } break; - default: { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 3; - r_error.expected = 3; - r_ret = Variant(); - - } break; - } - - } break; - case RESOURCE_LOAD: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = Variant(); - } else { - r_ret = ResourceLoader::load(*p_args[0]); - } - - } break; - case INST2DICT: { - VALIDATE_ARG_COUNT(1); - - if (p_args[0]->get_type() == Variant::NIL) { - r_ret = Variant(); - } else if (p_args[0]->get_type() != Variant::OBJECT) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_ret = Variant(); - } else { - Object *obj = *p_args[0]; - if (!obj) { - r_ret = Variant(); - - } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::DICTIONARY; - r_ret = RTR("Not a script with an instance"); - return; - } else { - GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance()); - Ref<GDScript> base = ins->get_script(); - if (base.is_null()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::DICTIONARY; - r_ret = RTR("Not based on a script"); - return; - } - - GDScript *p = base.ptr(); - Vector<StringName> sname; - - while (p->_owner) { - sname.push_back(p->name); - p = p->_owner; - } - sname.invert(); - - if (!p->path.is_resource_file()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::DICTIONARY; - r_ret = Variant(); - - r_ret = RTR("Not based on a resource file"); - - return; - } - - NodePath cp(sname, Vector<StringName>(), false); - - Dictionary d; - d["@subpath"] = cp; - d["@path"] = p->get_path(); - - for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) { - if (!d.has(E->key())) { - d[E->key()] = ins->members[E->get().index]; - } - } - r_ret = d; - } - } - - } break; - case DICT2INST: { - VALIDATE_ARG_COUNT(1); - - if (p_args[0]->get_type() != Variant::DICTIONARY) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::DICTIONARY; - r_ret = Variant(); - - return; - } - - Dictionary d = *p_args[0]; - - if (!d.has("@path")) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = RTR("Invalid instance dictionary format (missing @path)"); - - return; - } - - Ref<Script> scr = ResourceLoader::load(d["@path"]); - if (!scr.is_valid()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = RTR("Invalid instance dictionary format (can't load script at @path)"); - return; - } - - Ref<GDScript> gdscr = scr; - - if (!gdscr.is_valid()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = Variant(); - r_ret = RTR("Invalid instance dictionary format (invalid script at @path)"); - return; - } - - NodePath sub; - if (d.has("@subpath")) { - sub = d["@subpath"]; - } - - for (int i = 0; i < sub.get_name_count(); i++) { - gdscr = gdscr->subclasses[sub.get_name(i)]; - if (!gdscr.is_valid()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = Variant(); - r_ret = RTR("Invalid instance dictionary (invalid subclasses)"); - return; - } - } - r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error); - - if (r_error.error != Callable::CallError::CALL_OK) { - r_ret = Variant(); - return; - } - - GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(r_ret)->get_script_instance()); - Ref<GDScript> gd_ref = ins->get_script(); - - for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { - if (d.has(E->key())) { - ins->members.write[E->get().index] = d[E->key()]; - } - } - - } break; - case VALIDATE_JSON: { - VALIDATE_ARG_COUNT(1); - - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = Variant(); - return; - } - - String errs; - int errl; - - Error err = JSON::parse(*p_args[0], r_ret, errs, errl); - - if (err != OK) { - r_ret = itos(errl) + ":" + errs; - } else { - r_ret = ""; - } - - } break; - case PARSE_JSON: { - VALIDATE_ARG_COUNT(1); - - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::STRING; - r_ret = Variant(); - return; - } - - String errs; - int errl; - - Error err = JSON::parse(*p_args[0], r_ret, errs, errl); - - if (err != OK) { - r_ret = Variant(); - ERR_PRINT(vformat("Error parsing JSON at line %s: %s", errl, errs)); - } - - } break; - case TO_JSON: { - VALIDATE_ARG_COUNT(1); - - r_ret = JSON::print(*p_args[0]); - } break; - case HASH: { - VALIDATE_ARG_COUNT(1); - r_ret = p_args[0]->hash(); - - } break; - case COLOR8: { - if (p_arg_count < 3) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 3; - r_ret = Variant(); - - return; - } - if (p_arg_count > 4) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 4; - r_ret = Variant(); - - return; - } - - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - - Color color((float)*p_args[0] / 255.0f, (float)*p_args[1] / 255.0f, (float)*p_args[2] / 255.0f); - - if (p_arg_count == 4) { - VALIDATE_ARG_NUM(3); - color.a = (float)*p_args[3] / 255.0f; - } - - r_ret = color; - - } break; - case COLORN: { - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_ret = Variant(); - return; - } - - if (p_arg_count > 2) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 2; - r_ret = Variant(); - return; - } - - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_ret = Variant(); - } else { - Color color = Color::named(*p_args[0]); - if (p_arg_count == 2) { - VALIDATE_ARG_NUM(1); - color.a = *p_args[1]; - } - r_ret = color; - } - - } break; - - case PRINT_STACK: { - VALIDATE_ARG_COUNT(0); - - ScriptLanguage *script = GDScriptLanguage::get_singleton(); - for (int i = 0; i < script->debug_get_stack_level_count(); i++) { - print_line("Frame " + itos(i) + " - " + script->debug_get_stack_level_source(i) + ":" + itos(script->debug_get_stack_level_line(i)) + " in function '" + script->debug_get_stack_level_function(i) + "'"); - }; - } break; - - case GET_STACK: { - VALIDATE_ARG_COUNT(0); - - ScriptLanguage *script = GDScriptLanguage::get_singleton(); - Array ret; - for (int i = 0; i < script->debug_get_stack_level_count(); i++) { - Dictionary frame; - frame["source"] = script->debug_get_stack_level_source(i); - frame["function"] = script->debug_get_stack_level_function(i); - frame["line"] = script->debug_get_stack_level_line(i); - ret.push_back(frame); - }; - r_ret = ret; - } break; - - case INSTANCE_FROM_ID: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::INT && p_args[0]->get_type() != Variant::FLOAT) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::INT; - r_ret = Variant(); - break; - } - - ObjectID id = *p_args[0]; - r_ret = ObjectDB::get_instance(id); - - } break; - case LEN: { - VALIDATE_ARG_COUNT(1); - switch (p_args[0]->get_type()) { - case Variant::STRING: { - String d = *p_args[0]; - r_ret = d.length(); - } break; - case Variant::DICTIONARY: { - Dictionary d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::ARRAY: { - Array d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_BYTE_ARRAY: { - Vector<uint8_t> d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_INT32_ARRAY: { - Vector<int32_t> d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_INT64_ARRAY: { - Vector<int64_t> d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_FLOAT32_ARRAY: { - Vector<float> d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_FLOAT64_ARRAY: { - Vector<double> d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_STRING_ARRAY: { - Vector<String> d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_VECTOR2_ARRAY: { - Vector<Vector2> d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_VECTOR3_ARRAY: { - Vector<Vector3> d = *p_args[0]; - r_ret = d.size(); - } break; - case Variant::PACKED_COLOR_ARRAY: { - Vector<Color> d = *p_args[0]; - r_ret = d.size(); - } break; - default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = Variant(); - r_ret = RTR("Object can't provide a length."); - } - } - - } break; - case IS_INSTANCE_VALID: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::OBJECT) { - r_ret = false; - } else { - Object *obj = p_args[0]->get_validated_object(); - r_ret = obj != nullptr; - } - - } break; - case FUNC_MAX: { - ERR_FAIL(); - } break; - } -} - -bool GDScriptFunctions::is_deterministic(Function p_func) { - //man i couldn't have chosen a worse function name, - //way too controversial.. - - switch (p_func) { - case MATH_SIN: - case MATH_COS: - case MATH_TAN: - case MATH_SINH: - case MATH_COSH: - case MATH_TANH: - case MATH_ASIN: - case MATH_ACOS: - case MATH_ATAN: - case MATH_ATAN2: - case MATH_SQRT: - case MATH_FMOD: - case MATH_FPOSMOD: - case MATH_POSMOD: - case MATH_FLOOR: - case MATH_CEIL: - case MATH_ROUND: - case MATH_ABS: - case MATH_SIGN: - case MATH_POW: - case MATH_LOG: - case MATH_EXP: - case MATH_ISNAN: - case MATH_ISINF: - case MATH_EASE: - case MATH_STEP_DECIMALS: - case MATH_STEPIFY: - case MATH_LERP: - case MATH_INVERSE_LERP: - case MATH_RANGE_LERP: - case MATH_SMOOTHSTEP: - case MATH_MOVE_TOWARD: - case MATH_DECTIME: - case MATH_DEG2RAD: - case MATH_RAD2DEG: - case MATH_LINEAR2DB: - case MATH_DB2LINEAR: - case MATH_POLAR2CARTESIAN: - case MATH_CARTESIAN2POLAR: - case MATH_WRAP: - case MATH_WRAPF: - case LOGIC_MAX: - case LOGIC_MIN: - case LOGIC_CLAMP: - case LOGIC_NEAREST_PO2: - case TYPE_CONVERT: - case TYPE_OF: - case TYPE_EXISTS: - case TEXT_CHAR: - case TEXT_ORD: - case TEXT_STR: - case COLOR8: - case LEN: - // enable for debug only, otherwise not desirable - case GEN_RANGE: - return true; - default: - return false; - } - - return false; -} - -MethodInfo GDScriptFunctions::get_info(Function p_func) { -#ifdef DEBUG_ENABLED - //using a switch, so the compiler generates a jumptable - - switch (p_func) { - case MATH_SIN: { - MethodInfo mi("sin", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - - } break; - case MATH_COS: { - MethodInfo mi("cos", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_TAN: { - MethodInfo mi("tan", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SINH: { - MethodInfo mi("sinh", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_COSH: { - MethodInfo mi("cosh", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_TANH: { - MethodInfo mi("tanh", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ASIN: { - MethodInfo mi("asin", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ACOS: { - MethodInfo mi("acos", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ATAN: { - MethodInfo mi("atan", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ATAN2: { - MethodInfo mi("atan2", PropertyInfo(Variant::FLOAT, "y"), PropertyInfo(Variant::FLOAT, "x")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SQRT: { - MethodInfo mi("sqrt", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_FMOD: { - MethodInfo mi("fmod", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_FPOSMOD: { - MethodInfo mi("fposmod", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_POSMOD: { - MethodInfo mi("posmod", PropertyInfo(Variant::INT, "a"), PropertyInfo(Variant::INT, "b")); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_FLOOR: { - MethodInfo mi("floor", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_CEIL: { - MethodInfo mi("ceil", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ROUND: { - MethodInfo mi("round", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ABS: { - MethodInfo mi("abs", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SIGN: { - MethodInfo mi("sign", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_POW: { - MethodInfo mi("pow", PropertyInfo(Variant::FLOAT, "base"), PropertyInfo(Variant::FLOAT, "exp")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_LOG: { - MethodInfo mi("log", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_EXP: { - MethodInfo mi("exp", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ISNAN: { - MethodInfo mi("is_nan", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - case MATH_ISINF: { - MethodInfo mi("is_inf", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - case MATH_ISEQUALAPPROX: { - MethodInfo mi("is_equal_approx", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - case MATH_ISZEROAPPROX: { - MethodInfo mi("is_zero_approx", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - case MATH_EASE: { - MethodInfo mi("ease", PropertyInfo(Variant::FLOAT, "s"), PropertyInfo(Variant::FLOAT, "curve")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_STEP_DECIMALS: { - MethodInfo mi("step_decimals", PropertyInfo(Variant::FLOAT, "step")); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_STEPIFY: { - MethodInfo mi("stepify", PropertyInfo(Variant::FLOAT, "s"), PropertyInfo(Variant::FLOAT, "step")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_LERP: { - MethodInfo mi("lerp", PropertyInfo(Variant::NIL, "from"), PropertyInfo(Variant::NIL, "to"), PropertyInfo(Variant::FLOAT, "weight")); - mi.return_val.type = Variant::NIL; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - return mi; - } break; - case MATH_LERP_ANGLE: { - MethodInfo mi("lerp_angle", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "weight")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_INVERSE_LERP: { - MethodInfo mi("inverse_lerp", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "weight")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RANGE_LERP: { - MethodInfo mi("range_lerp", PropertyInfo(Variant::FLOAT, "value"), PropertyInfo(Variant::FLOAT, "istart"), PropertyInfo(Variant::FLOAT, "istop"), PropertyInfo(Variant::FLOAT, "ostart"), PropertyInfo(Variant::FLOAT, "ostop")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SMOOTHSTEP: { - MethodInfo mi("smoothstep", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_MOVE_TOWARD: { - MethodInfo mi("move_toward", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "delta")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_DECTIME: { - MethodInfo mi("dectime", PropertyInfo(Variant::FLOAT, "value"), PropertyInfo(Variant::FLOAT, "amount"), PropertyInfo(Variant::FLOAT, "step")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RANDOMIZE: { - MethodInfo mi("randomize"); - mi.return_val.type = Variant::NIL; - return mi; - } break; - case MATH_RANDI: { - MethodInfo mi("randi"); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_RANDF: { - MethodInfo mi("randf"); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RANDF_RANGE: { - MethodInfo mi("randf_range", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RANDI_RANGE: { - MethodInfo mi("randi_range", PropertyInfo(Variant::INT, "from"), PropertyInfo(Variant::INT, "to")); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_SEED: { - MethodInfo mi("seed", PropertyInfo(Variant::INT, "seed")); - mi.return_val.type = Variant::NIL; - return mi; - } break; - case MATH_RANDSEED: { - MethodInfo mi("rand_seed", PropertyInfo(Variant::INT, "seed")); - mi.return_val.type = Variant::ARRAY; - return mi; - } break; - case MATH_DEG2RAD: { - MethodInfo mi("deg2rad", PropertyInfo(Variant::FLOAT, "deg")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RAD2DEG: { - MethodInfo mi("rad2deg", PropertyInfo(Variant::FLOAT, "rad")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_LINEAR2DB: { - MethodInfo mi("linear2db", PropertyInfo(Variant::FLOAT, "nrg")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_DB2LINEAR: { - MethodInfo mi("db2linear", PropertyInfo(Variant::FLOAT, "db")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_POLAR2CARTESIAN: { - MethodInfo mi("polar2cartesian", PropertyInfo(Variant::FLOAT, "r"), PropertyInfo(Variant::FLOAT, "th")); - mi.return_val.type = Variant::VECTOR2; - return mi; - } break; - case MATH_CARTESIAN2POLAR: { - MethodInfo mi("cartesian2polar", PropertyInfo(Variant::FLOAT, "x"), PropertyInfo(Variant::FLOAT, "y")); - mi.return_val.type = Variant::VECTOR2; - return mi; - } break; - case MATH_WRAP: { - MethodInfo mi("wrapi", PropertyInfo(Variant::INT, "value"), PropertyInfo(Variant::INT, "min"), PropertyInfo(Variant::INT, "max")); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_WRAPF: { - MethodInfo mi("wrapf", PropertyInfo(Variant::FLOAT, "value"), PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case LOGIC_MAX: { - MethodInfo mi("max", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::FLOAT; - return mi; - - } break; - case LOGIC_MIN: { - MethodInfo mi("min", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case LOGIC_CLAMP: { - MethodInfo mi("clamp", PropertyInfo(Variant::FLOAT, "value"), PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case LOGIC_NEAREST_PO2: { - MethodInfo mi("nearest_po2", PropertyInfo(Variant::INT, "value")); - mi.return_val.type = Variant::INT; - return mi; - } break; - case OBJ_WEAKREF: { - MethodInfo mi("weakref", PropertyInfo(Variant::OBJECT, "obj")); - mi.return_val.type = Variant::OBJECT; - mi.return_val.class_name = "WeakRef"; - - return mi; - - } break; - case TYPE_CONVERT: { - MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::INT, "type")); - mi.return_val.type = Variant::NIL; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - return mi; - } break; - case TYPE_OF: { - MethodInfo mi("typeof", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); - mi.return_val.type = Variant::INT; - return mi; - - } break; - case TYPE_EXISTS: { - MethodInfo mi("type_exists", PropertyInfo(Variant::STRING, "type")); - mi.return_val.type = Variant::BOOL; - return mi; - - } break; - case TEXT_CHAR: { - MethodInfo mi("char", PropertyInfo(Variant::INT, "code")); - mi.return_val.type = Variant::STRING; - return mi; - - } break; - case TEXT_ORD: { - MethodInfo mi("ord", PropertyInfo(Variant::STRING, "char")); - mi.return_val.type = Variant::INT; - return mi; - - } break; - case TEXT_STR: { - MethodInfo mi("str"); - mi.return_val.type = Variant::STRING; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINT: { - MethodInfo mi("print"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINT_TABBED: { - MethodInfo mi("printt"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINT_SPACED: { - MethodInfo mi("prints"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINTERR: { - MethodInfo mi("printerr"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINTRAW: { - MethodInfo mi("printraw"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINT_DEBUG: { - MethodInfo mi("print_debug"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case PUSH_ERROR: { - MethodInfo mi(Variant::NIL, "push_error", PropertyInfo(Variant::STRING, "message")); - mi.return_val.type = Variant::NIL; - return mi; - - } break; - case PUSH_WARNING: { - MethodInfo mi(Variant::NIL, "push_warning", PropertyInfo(Variant::STRING, "message")); - mi.return_val.type = Variant::NIL; - return mi; - - } break; - case VAR_TO_STR: { - MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); - mi.return_val.type = Variant::STRING; - return mi; - } break; - case STR_TO_VAR: { - MethodInfo mi(Variant::NIL, "str2var", PropertyInfo(Variant::STRING, "string")); - mi.return_val.type = Variant::NIL; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - return mi; - } break; - case VAR_TO_BYTES: { - MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::BOOL, "full_objects")); - mi.default_arguments.push_back(false); - mi.return_val.type = Variant::PACKED_BYTE_ARRAY; - return mi; - } break; - case BYTES_TO_VAR: { - MethodInfo mi(Variant::NIL, "bytes2var", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "bytes"), PropertyInfo(Variant::BOOL, "allow_objects")); - mi.default_arguments.push_back(false); - mi.return_val.type = Variant::NIL; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - return mi; - } break; - case GEN_RANGE: { - MethodInfo mi("range"); - mi.return_val.type = Variant::ARRAY; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - } break; - case RESOURCE_LOAD: { - MethodInfo mi("load", PropertyInfo(Variant::STRING, "path")); - mi.return_val.type = Variant::OBJECT; - mi.return_val.class_name = "Resource"; - return mi; - } break; - case INST2DICT: { - MethodInfo mi("inst2dict", PropertyInfo(Variant::OBJECT, "inst")); - mi.return_val.type = Variant::DICTIONARY; - return mi; - } break; - case DICT2INST: { - MethodInfo mi("dict2inst", PropertyInfo(Variant::DICTIONARY, "dict")); - mi.return_val.type = Variant::OBJECT; - return mi; - } break; - case VALIDATE_JSON: { - MethodInfo mi("validate_json", PropertyInfo(Variant::STRING, "json")); - mi.return_val.type = Variant::STRING; - return mi; - } break; - case PARSE_JSON: { - MethodInfo mi(Variant::NIL, "parse_json", PropertyInfo(Variant::STRING, "json")); - mi.return_val.type = Variant::NIL; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - return mi; - } break; - case TO_JSON: { - MethodInfo mi("to_json", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); - mi.return_val.type = Variant::STRING; - return mi; - } break; - case HASH: { - MethodInfo mi("hash", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); - mi.return_val.type = Variant::INT; - return mi; - } break; - case COLOR8: { - MethodInfo mi("Color8", PropertyInfo(Variant::INT, "r8"), PropertyInfo(Variant::INT, "g8"), PropertyInfo(Variant::INT, "b8"), PropertyInfo(Variant::INT, "a8")); - mi.default_arguments.push_back(255); - mi.return_val.type = Variant::COLOR; - return mi; - } break; - case COLORN: { - MethodInfo mi("ColorN", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "alpha")); - mi.default_arguments.push_back(1.0f); - mi.return_val.type = Variant::COLOR; - return mi; - } break; - - case PRINT_STACK: { - MethodInfo mi("print_stack"); - mi.return_val.type = Variant::NIL; - return mi; - } break; - case GET_STACK: { - MethodInfo mi("get_stack"); - mi.return_val.type = Variant::ARRAY; - return mi; - } break; - - case INSTANCE_FROM_ID: { - MethodInfo mi("instance_from_id", PropertyInfo(Variant::INT, "instance_id")); - mi.return_val.type = Variant::OBJECT; - return mi; - } break; - case LEN: { - MethodInfo mi("len", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); - mi.return_val.type = Variant::INT; - return mi; - } break; - case IS_INSTANCE_VALID: { - MethodInfo mi("is_instance_valid", PropertyInfo(Variant::OBJECT, "instance")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - default: { - ERR_FAIL_V(MethodInfo()); - } break; - } -#endif - MethodInfo mi; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - return mi; -} diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 48fca16ab1..0c93702bfb 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -47,7 +47,7 @@ static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { - if (builtin_types.empty()) { + if (builtin_types.is_empty()) { builtin_types["bool"] = Variant::BOOL; builtin_types["int"] = Variant::INT; builtin_types["float"] = Variant::FLOAT; @@ -98,15 +98,6 @@ void GDScriptParser::cleanup() { builtin_types.clear(); } -GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringName &p_name) { - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - if (p_name == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) { - return GDScriptFunctions::Function(i); - } - } - return GDScriptFunctions::FUNC_MAX; -} - void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { List<StringName> keys; valid_annotations.get_key_list(&keys); @@ -186,16 +177,16 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { ERR_FAIL_COND(p_source == nullptr); Vector<String> symbols; - if (!p_symbol1.empty()) { + if (!p_symbol1.is_empty()) { symbols.push_back(p_symbol1); } - if (!p_symbol2.empty()) { + if (!p_symbol2.is_empty()) { symbols.push_back(p_symbol2); } - if (!p_symbol3.empty()) { + if (!p_symbol3.is_empty()) { symbols.push_back(p_symbol3); } - if (!p_symbol4.empty()) { + if (!p_symbol4.is_empty()) { symbols.push_back(p_symbol4); } push_warning(p_source, p_code, symbols); @@ -293,7 +284,7 @@ void GDScriptParser::pop_completion_call() { if (!for_completion) { return; } - ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to pop empty completion call stack"); + ERR_FAIL_COND_MSG(completion_call_stack.is_empty(), "Trying to pop empty completion call stack"); completion_call_stack.pop_back(); } @@ -301,7 +292,7 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) { if (!for_completion || passed_cursor) { return; } - ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to set argument on empty completion call stack"); + ERR_FAIL_COND_MSG(completion_call_stack.is_empty(), "Trying to set argument on empty completion call stack"); completion_call_stack.back()->get().argument = p_argument; } @@ -367,7 +358,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ } #endif - if (errors.empty()) { + if (errors.is_empty()) { return OK; } else { return ERR_PARSE_ERROR; @@ -378,7 +369,7 @@ GDScriptTokenizer::Token GDScriptParser::advance() { if (current.type == GDScriptTokenizer::Token::TK_EOF) { ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream."); } - if (for_completion && !completion_call_stack.empty()) { + if (for_completion && !completion_call_stack.is_empty()) { if (completion_call.call == nullptr && tokenizer.is_past_cursor()) { completion_call = completion_call_stack.back()->get(); passed_cursor = true; @@ -509,7 +500,7 @@ void GDScriptParser::parse_program() { // Order here doesn't matter, but there should be only one of each at most. switch (current.type) { case GDScriptTokenizer::Token::CLASS_NAME: - if (!annotation_stack.empty()) { + if (!annotation_stack.is_empty()) { push_error(R"("class_name" should be used before annotations.)"); } advance(); @@ -520,7 +511,7 @@ void GDScriptParser::parse_program() { } break; case GDScriptTokenizer::Token::EXTENDS: - if (!annotation_stack.empty()) { + if (!annotation_stack.is_empty()) { push_error(R"("extends" should be used before annotations.)"); } advance(); @@ -684,7 +675,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() #endif // TOOLS_ENABLED // Consume annotations. - while (!annotation_stack.empty()) { + while (!annotation_stack.is_empty()) { AnnotationNode *last_annotation = annotation_stack.back()->get(); if (last_annotation->applies_to(p_target)) { last_annotation->apply(this, member); @@ -1707,7 +1698,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { branch->patterns.push_back(pattern); } while (match(GDScriptTokenizer::Token::COMMA)); - if (branch->patterns.empty()) { + if (branch->patterns.is_empty()) { push_error(R"(No pattern found for "match" branch.)"); } @@ -2553,7 +2544,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre // Arguments. CompletionType ct = COMPLETION_CALL_ARGUMENTS; - if (get_builtin_function(call->function_name) == GDScriptFunctions::RESOURCE_LOAD) { + if (call->function_name == "load") { ct = COMPLETION_RESOURCE_PATH; } push_completion_call(call); @@ -2760,7 +2751,7 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { } String line_join = (in_codeblock) ? "\n" : " "; - doc = (doc.empty()) ? doc_line : doc + line_join + doc_line; + doc = (doc.is_empty()) ? doc_line : doc + line_join + doc_line; line++; } @@ -2854,7 +2845,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & mode = TUTORIALS; in_codeblock = false; - } else if (striped_line.empty()) { + } else if (striped_line.is_empty()) { continue; } else { // Tutorial docs are single line, we need a @tag after it. @@ -3289,11 +3280,11 @@ String GDScriptParser::DataType::to_string() const { return script_type->get_class_name().operator String(); } String name = script_type->get_name(); - if (!name.empty()) { + if (!name.is_empty()) { return name; } name = script_path; - if (!name.empty()) { + if (!name.is_empty()) { return name; } return native_type.operator String(); @@ -3338,7 +3329,7 @@ void GDScriptParser::TreePrinter::decrease_indent() { } void GDScriptParser::TreePrinter::push_line(const String &p_line) { - if (!p_line.empty()) { + if (!p_line.is_empty()) { push_text(p_line); } printed += "\n"; @@ -3547,7 +3538,7 @@ void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) { if (p_class->extends_used) { bool first = true; push_text(" Extends "); - if (!p_class->extends_path.empty()) { + if (!p_class->extends_path.is_empty()) { push_text(vformat(R"("%s")", p_class->extends_path)); first = false; } @@ -4009,7 +4000,7 @@ void GDScriptParser::TreePrinter::print_ternary_op(TernaryOpNode *p_ternary_op) } void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) { - if (p_type->type_chain.empty()) { + if (p_type->type_chain.is_empty()) { push_text("Void"); } else { for (int i = 0; i < p_type->type_chain.size(); i++) { @@ -4129,7 +4120,7 @@ void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { if (p_parser.is_tool()) { push_line("@tool"); } - if (!p_parser.get_tree()->icon_path.empty()) { + if (!p_parser.get_tree()->icon_path.is_empty()) { push_text(R"(@icon (")"); push_text(p_parser.get_tree()->icon_path); push_line("\")"); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 44605bc20f..4cecdc6970 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -43,7 +43,6 @@ #include "core/templates/vector.h" #include "core/variant/variant.h" #include "gdscript_cache.h" -#include "gdscript_functions.h" #include "gdscript_tokenizer.h" #ifdef DEBUG_ENABLED @@ -1314,7 +1313,6 @@ public: ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } static Variant::Type get_builtin_type(const StringName &p_type); - static GDScriptFunctions::Function get_builtin_function(const StringName &p_name); CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index ac43105254..febb066223 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -221,7 +221,7 @@ String GDScriptTokenizer::get_token_name(Token::Type p_token_type) { void GDScriptTokenizer::set_source_code(const String &p_source_code) { source = p_source_code; - if (source.empty()) { + if (source.is_empty()) { _source = U""; } else { _source = source.ptr(); @@ -287,7 +287,7 @@ void GDScriptTokenizer::push_paren(char32_t p_char) { } bool GDScriptTokenizer::pop_paren(char32_t p_expected) { - if (paren_stack.empty()) { + if (paren_stack.is_empty()) { return false; } char32_t actual = paren_stack.back()->get(); @@ -405,7 +405,7 @@ void GDScriptTokenizer::push_error(const Token &p_error) { } GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(char32_t p_paren) { - if (paren_stack.empty()) { + if (paren_stack.is_empty()) { return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren)); } Token error = make_error(vformat("Closing \"%c\" doesn't match the opening \"%c\".", p_paren, paren_stack.back()->get())); diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index f236c86f9f..70556d7166 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -230,7 +230,7 @@ private: _FORCE_INLINE_ bool _is_at_end() { return position >= length; } _FORCE_INLINE_ char32_t _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; } int indent_level() const { return indent_stack.size(); } - bool has_error() const { return !error_stack.empty(); } + bool has_error() const { return !error_stack.is_empty(); } Token pop_error(); char32_t _advance(); void _skip_whitespace(); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp new file mode 100644 index 0000000000..b1780446d0 --- /dev/null +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -0,0 +1,718 @@ +/*************************************************************************/ +/* gdscript_utility_functions.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "gdscript_utility_functions.h" + +#include "core/io/resource_loader.h" +#include "core/object/class_db.h" +#include "core/object/method_bind.h" +#include "core/object/object.h" +#include "core/templates/oa_hash_map.h" +#include "core/templates/vector.h" +#include "gdscript.h" + +#ifdef DEBUG_ENABLED + +#define VALIDATE_ARG_COUNT(m_count) \ + if (p_arg_count < m_count) { \ + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ + r_error.argument = m_count; \ + r_error.expected = m_count; \ + *r_ret = Variant(); \ + return; \ + } \ + if (p_arg_count > m_count) { \ + r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \ + r_error.argument = m_count; \ + r_error.expected = m_count; \ + *r_ret = Variant(); \ + return; \ + } + +#define VALIDATE_ARG_INT(m_arg) \ + if (p_args[m_arg]->get_type() != Variant::INT) { \ + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \ + r_error.argument = m_arg; \ + r_error.expected = Variant::INT; \ + *r_ret = Variant(); \ + return; \ + } + +#define VALIDATE_ARG_NUM(m_arg) \ + if (!p_args[m_arg]->is_num()) { \ + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \ + r_error.argument = m_arg; \ + r_error.expected = Variant::FLOAT; \ + *r_ret = Variant(); \ + return; \ + } + +#else + +#define VALIDATE_ARG_COUNT(m_count) +#define VALIDATE_ARG_INT(m_arg) +#define VALIDATE_ARG_NUM(m_arg) + +#endif + +struct GDScriptUtilityFunctionsDefinitions { + static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_INT(1); + int type = *p_args[1]; + if (type < 0 || type >= Variant::VARIANT_MAX) { + *r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::INT; + return; + + } else { + Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error); + } + } + + static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + *r_ret = ClassDB::class_exists(*p_args[0]); + } + + static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_INT(0); + char32_t result[2] = { *p_args[0], 0 }; + *r_ret = String(result); + } + + static inline void str(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + if (p_arg_count < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 1; + *r_ret = Variant(); + return; + } + + String str; + for (int i = 0; i < p_arg_count; i++) { + String os = p_args[i]->operator String(); + + if (i == 0) { + str = os; + } else { + str += os; + } + } + *r_ret = str; + } + + static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + switch (p_arg_count) { + case 0: { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 1; + r_error.expected = 1; + *r_ret = Variant(); + } break; + case 1: { + VALIDATE_ARG_NUM(0); + int count = *p_args[0]; + Array arr; + if (count <= 0) { + *r_ret = arr; + return; + } + Error err = arr.resize(count); + if (err != OK) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + *r_ret = Variant(); + return; + } + + for (int i = 0; i < count; i++) { + arr[i] = i; + } + + *r_ret = arr; + } break; + case 2: { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + int from = *p_args[0]; + int to = *p_args[1]; + + Array arr; + if (from >= to) { + *r_ret = arr; + return; + } + Error err = arr.resize(to - from); + if (err != OK) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + *r_ret = Variant(); + return; + } + for (int i = from; i < to; i++) { + arr[i - from] = i; + } + *r_ret = arr; + } break; + case 3: { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + + int from = *p_args[0]; + int to = *p_args[1]; + int incr = *p_args[2]; + if (incr == 0) { + *r_ret = RTR("Step argument is zero!"); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return; + } + + Array arr; + if (from >= to && incr > 0) { + *r_ret = arr; + return; + } + if (from <= to && incr < 0) { + *r_ret = arr; + return; + } + + // Calculate how many. + int count = 0; + if (incr > 0) { + count = ((to - from - 1) / incr) + 1; + } else { + count = ((from - to - 1) / -incr) + 1; + } + + Error err = arr.resize(count); + + if (err != OK) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + *r_ret = Variant(); + return; + } + + if (incr > 0) { + int idx = 0; + for (int i = from; i < to; i += incr) { + arr[idx++] = i; + } + } else { + int idx = 0; + for (int i = from; i > to; i += incr) { + arr[idx++] = i; + } + } + + *r_ret = arr; + } break; + default: { + r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.argument = 3; + r_error.expected = 3; + *r_ret = Variant(); + + } break; + } + } + + static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + *r_ret = Variant(); + } else { + *r_ret = ResourceLoader::load(*p_args[0]); + } + } + + static inline void inst2dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type() == Variant::NIL) { + *r_ret = Variant(); + } else if (p_args[0]->get_type() != Variant::OBJECT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + *r_ret = Variant(); + } else { + Object *obj = *p_args[0]; + if (!obj) { + *r_ret = Variant(); + + } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + *r_ret = RTR("Not a script with an instance"); + return; + } else { + GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance()); + Ref<GDScript> base = ins->get_script(); + if (base.is_null()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + *r_ret = RTR("Not based on a script"); + return; + } + + GDScript *p = base.ptr(); + Vector<StringName> sname; + + while (p->_owner) { + sname.push_back(p->name); + p = p->_owner; + } + sname.invert(); + + if (!p->path.is_resource_file()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + *r_ret = Variant(); + + *r_ret = RTR("Not based on a resource file"); + + return; + } + + NodePath cp(sname, Vector<StringName>(), false); + + Dictionary d; + d["@subpath"] = cp; + d["@path"] = p->get_path(); + + for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) { + if (!d.has(E->key())) { + d[E->key()] = ins->members[E->get().index]; + } + } + *r_ret = d; + } + } + } + + static inline void dict2inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type() != Variant::DICTIONARY) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + *r_ret = Variant(); + + return; + } + + Dictionary d = *p_args[0]; + + if (!d.has("@path")) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::OBJECT; + *r_ret = RTR("Invalid instance dictionary format (missing @path)"); + + return; + } + + Ref<Script> scr = ResourceLoader::load(d["@path"]); + if (!scr.is_valid()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::OBJECT; + *r_ret = RTR("Invalid instance dictionary format (can't load script at @path)"); + return; + } + + Ref<GDScript> gdscr = scr; + + if (!gdscr.is_valid()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::OBJECT; + *r_ret = Variant(); + *r_ret = RTR("Invalid instance dictionary format (invalid script at @path)"); + return; + } + + NodePath sub; + if (d.has("@subpath")) { + sub = d["@subpath"]; + } + + for (int i = 0; i < sub.get_name_count(); i++) { + gdscr = gdscr->subclasses[sub.get_name(i)]; + if (!gdscr.is_valid()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::OBJECT; + *r_ret = Variant(); + *r_ret = RTR("Invalid instance dictionary (invalid subclasses)"); + return; + } + } + *r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error); + + if (r_error.error != Callable::CallError::CALL_OK) { + *r_ret = Variant(); + return; + } + + GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance()); + Ref<GDScript> gd_ref = ins->get_script(); + + for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { + if (d.has(E->key())) { + ins->members.write[E->get().index] = d[E->key()]; + } + } + } + + static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + if (p_arg_count < 3) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 3; + *r_ret = Variant(); + return; + } + if (p_arg_count > 4) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.argument = 4; + *r_ret = Variant(); + return; + } + + VALIDATE_ARG_INT(0); + VALIDATE_ARG_INT(1); + VALIDATE_ARG_INT(2); + + Color color((int64_t)*p_args[0] / 255.0f, (int64_t)*p_args[1] / 255.0f, (int64_t)*p_args[2] / 255.0f); + + if (p_arg_count == 4) { + VALIDATE_ARG_INT(3); + color.a = (int64_t)*p_args[3] / 255.0f; + } + + *r_ret = color; + } + + static inline void print_debug(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + String str; + for (int i = 0; i < p_arg_count; i++) { + str += p_args[i]->operator String(); + } + + ScriptLanguage *script = GDScriptLanguage::get_singleton(); + if (script->debug_get_stack_level_count() > 0) { + str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; + } + + print_line(str); + *r_ret = Variant(); + } + + static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(0); + + ScriptLanguage *script = GDScriptLanguage::get_singleton(); + for (int i = 0; i < script->debug_get_stack_level_count(); i++) { + print_line("Frame " + itos(i) + " - " + script->debug_get_stack_level_source(i) + ":" + itos(script->debug_get_stack_level_line(i)) + " in function '" + script->debug_get_stack_level_function(i) + "'"); + }; + } + + static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(0); + + ScriptLanguage *script = GDScriptLanguage::get_singleton(); + Array ret; + for (int i = 0; i < script->debug_get_stack_level_count(); i++) { + Dictionary frame; + frame["source"] = script->debug_get_stack_level_source(i); + frame["function"] = script->debug_get_stack_level_function(i); + frame["line"] = script->debug_get_stack_level_line(i); + ret.push_back(frame); + }; + *r_ret = ret; + } + + static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + switch (p_args[0]->get_type()) { + case Variant::STRING: { + String d = *p_args[0]; + *r_ret = d.length(); + } break; + case Variant::DICTIONARY: { + Dictionary d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::ARRAY: { + Array d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_BYTE_ARRAY: { + Vector<uint8_t> d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_INT32_ARRAY: { + Vector<int32_t> d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_INT64_ARRAY: { + Vector<int64_t> d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_FLOAT32_ARRAY: { + Vector<float> d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_FLOAT64_ARRAY: { + Vector<double> d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_STRING_ARRAY: { + Vector<String> d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_VECTOR2_ARRAY: { + Vector<Vector2> d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_VECTOR3_ARRAY: { + Vector<Vector3> d = *p_args[0]; + *r_ret = d.size(); + } break; + case Variant::PACKED_COLOR_ARRAY: { + Vector<Color> d = *p_args[0]; + *r_ret = d.size(); + } break; + default: { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type())); + } + } + } +}; + +struct GDScriptUtilityFunctionInfo { + GDScriptUtilityFunctions::FunctionPtr function; + MethodInfo info; + bool is_constant = false; +}; + +static OAHashMap<StringName, GDScriptUtilityFunctionInfo> utility_function_table; +static List<StringName> utility_function_name_table; + +static void _register_function(const String &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) { + StringName sname(p_name); + + ERR_FAIL_COND(utility_function_table.has(sname)); + + GDScriptUtilityFunctionInfo function; + function.function = p_function; + function.info = p_method_info; + function.is_constant = p_is_const; + + utility_function_table.insert(sname, function); + utility_function_name_table.push_back(sname); +} + +#define REGISTER_FUNC(m_func, m_is_const, m_return_type, ...) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name, __VA_ARGS__); \ + info.return_val.type = m_return_type; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_FUNC_NO_ARGS(m_func, m_is_const, m_return_type) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name); \ + info.return_val.type = m_return_type; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_VARARG_FUNC(m_func, m_is_const, m_return_type) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name); \ + info.return_val.type = m_return_type; \ + info.flags |= METHOD_FLAG_VARARG; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_VARIANT_FUNC(m_func, m_is_const, ...) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name, __VA_ARGS__); \ + info.return_val.type = Variant::NIL; \ + info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_CLASS_FUNC(m_func, m_is_const, m_return_type, ...) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name, __VA_ARGS__); \ + info.return_val.type = Variant::OBJECT; \ + info.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; \ + info.return_val.class_name = m_return_type; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_FUNC_DEF(m_func, m_is_const, m_default, m_return_type, ...) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name, __VA_ARGS__); \ + info.return_val.type = m_return_type; \ + info.default_arguments.push_back(m_default); \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define ARG(m_name, m_type) \ + PropertyInfo(m_type, m_name) + +#define VARARG(m_name) \ + PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT) + +void GDScriptUtilityFunctions::register_functions() { + REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT)); + REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME)); + REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT)); + REGISTER_VARARG_FUNC(str, true, Variant::STRING); + REGISTER_VARARG_FUNC(range, false, Variant::ARRAY); + REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING)); + REGISTER_FUNC(inst2dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT)); + REGISTER_FUNC(dict2inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY)); + REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT)); + REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL); + REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL); + REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY); + REGISTER_FUNC(len, true, Variant::INT, VARARG("var")); +} + +void GDScriptUtilityFunctions::unregister_functions() { + utility_function_name_table.clear(); + utility_function_table.clear(); +} + +GDScriptUtilityFunctions::FunctionPtr GDScriptUtilityFunctions::get_function(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, nullptr); + return info->function; +} + +bool GDScriptUtilityFunctions::has_function_return_value(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, false); + return info->info.return_val.type != Variant::NIL || bool(info->info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT); +} + +Variant::Type GDScriptUtilityFunctions::get_function_return_type(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, Variant::NIL); + return info->info.return_val.type; +} + +StringName GDScriptUtilityFunctions::get_function_return_class(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, StringName()); + return info->info.return_val.class_name; +} + +Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringName &p_function, int p_arg) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, Variant::NIL); + ERR_FAIL_COND_V(p_arg >= info->info.arguments.size(), Variant::NIL); + return info->info.arguments[p_arg].type; +} + +int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, 0); + return info->info.arguments.size(); +} + +bool GDScriptUtilityFunctions::is_function_vararg(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, false); + return (bool)(info->info.flags & METHOD_FLAG_VARARG); +} + +bool GDScriptUtilityFunctions::is_function_constant(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, false); + return info->is_constant; +} + +bool GDScriptUtilityFunctions::function_exists(const StringName &p_function) { + return utility_function_table.has(p_function); +} + +void GDScriptUtilityFunctions::get_function_list(List<StringName> *r_functions) { + for (const List<StringName>::Element *E = utility_function_name_table.front(); E; E = E->next()) { + r_functions->push_back(E->get()); + } +} + +MethodInfo GDScriptUtilityFunctions::get_function_info(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, MethodInfo()); + return info->info; +} diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_utility_functions.h index 005b49c5da..50867438d9 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_utility_functions.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gdscript_functions.h */ +/* gdscript_utility_functions.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,110 +28,31 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GDSCRIPT_FUNCTIONS_H -#define GDSCRIPT_FUNCTIONS_H +#ifndef GDSCRIPT_UTILITY_FUNCTIONS_H +#define GDSCRIPT_UTILITY_FUNCTIONS_H +#include "core/string/string_name.h" #include "core/variant/variant.h" -class GDScriptFunctions { +class GDScriptUtilityFunctions { public: - enum Function { - MATH_SIN, - MATH_COS, - MATH_TAN, - MATH_SINH, - MATH_COSH, - MATH_TANH, - MATH_ASIN, - MATH_ACOS, - MATH_ATAN, - MATH_ATAN2, - MATH_SQRT, - MATH_FMOD, - MATH_FPOSMOD, - MATH_POSMOD, - MATH_FLOOR, - MATH_CEIL, - MATH_ROUND, - MATH_ABS, - MATH_SIGN, - MATH_POW, - MATH_LOG, - MATH_EXP, - MATH_ISNAN, - MATH_ISINF, - MATH_ISEQUALAPPROX, - MATH_ISZEROAPPROX, - MATH_EASE, - MATH_STEP_DECIMALS, - MATH_STEPIFY, - MATH_LERP, - MATH_LERP_ANGLE, - MATH_INVERSE_LERP, - MATH_RANGE_LERP, - MATH_SMOOTHSTEP, - MATH_MOVE_TOWARD, - MATH_DECTIME, - MATH_RANDOMIZE, - MATH_RANDI, - MATH_RANDF, - MATH_RANDF_RANGE, - MATH_RANDI_RANGE, - MATH_SEED, - MATH_RANDSEED, - MATH_DEG2RAD, - MATH_RAD2DEG, - MATH_LINEAR2DB, - MATH_DB2LINEAR, - MATH_POLAR2CARTESIAN, - MATH_CARTESIAN2POLAR, - MATH_WRAP, - MATH_WRAPF, - LOGIC_MAX, - LOGIC_MIN, - LOGIC_CLAMP, - LOGIC_NEAREST_PO2, - OBJ_WEAKREF, - TYPE_CONVERT, - TYPE_OF, - TYPE_EXISTS, - TEXT_CHAR, - TEXT_ORD, - TEXT_STR, - TEXT_PRINT, - TEXT_PRINT_TABBED, - TEXT_PRINT_SPACED, - TEXT_PRINTERR, - TEXT_PRINTRAW, - TEXT_PRINT_DEBUG, - PUSH_ERROR, - PUSH_WARNING, - VAR_TO_STR, - STR_TO_VAR, - VAR_TO_BYTES, - BYTES_TO_VAR, - GEN_RANGE, - RESOURCE_LOAD, - INST2DICT, - DICT2INST, - VALIDATE_JSON, - PARSE_JSON, - TO_JSON, - HASH, - COLOR8, - COLORN, - PRINT_STACK, - GET_STACK, - INSTANCE_FROM_ID, - LEN, - IS_INSTANCE_VALID, - FUNC_MAX - }; + typedef void (*FunctionPtr)(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error); - static const char *get_func_name(Function p_func); - static void call(Function p_func, const Variant **p_args, int p_arg_count, Variant &r_ret, Callable::CallError &r_error); - static bool is_deterministic(Function p_func); - static MethodInfo get_info(Function p_func); + static FunctionPtr get_function(const StringName &p_function); + static bool has_function_return_value(const StringName &p_function); + static Variant::Type get_function_return_type(const StringName &p_function); + static StringName get_function_return_class(const StringName &p_function); + static Variant::Type get_function_argument_type(const StringName &p_function, int p_arg); + static int get_function_argument_count(const StringName &p_function, int p_arg); + static bool is_function_vararg(const StringName &p_function); + static bool is_function_constant(const StringName &p_function); + + static bool function_exists(const StringName &p_function); + static void get_function_list(List<StringName> *r_functions); + static MethodInfo get_function_info(const StringName &p_function); + + static void register_functions(); + static void unregister_functions(); }; -#endif // GDSCRIPT_FUNCTIONS_H +#endif // GDSCRIPT_UTILITY_FUNCTIONS_H diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 7942ee8d97..dacd7df498 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -33,7 +33,6 @@ #include "core/core_string_names.h" #include "core/os/os.h" #include "gdscript.h" -#include "gdscript_functions.h" Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const { int address = p_address & ADDR_MASK; @@ -220,7 +219,9 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CALL, \ &&OPCODE_CALL_RETURN, \ &&OPCODE_CALL_ASYNC, \ - &&OPCODE_CALL_BUILT_IN, \ + &&OPCODE_CALL_UTILITY, \ + &&OPCODE_CALL_UTILITY_VALIDATED, \ + &&OPCODE_CALL_GDSCRIPT_UTILITY, \ &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ &&OPCODE_CALL_SELF_BASE, \ &&OPCODE_CALL_METHOD_BIND, \ @@ -738,7 +739,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a const Variant::ValidatedKeyedSetter setter = _keyed_setters_ptr[index_setter]; bool valid; - setter(dst, index, value, valid); + setter(dst, index, value, &valid); #ifdef DEBUG_ENABLED if (!valid) { @@ -770,7 +771,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a int64_t int_index = *VariantInternal::get_int(index); bool oob; - setter(dst, int_index, value, oob); + setter(dst, int_index, value, &oob); #ifdef DEBUG_ENABLED if (oob) { @@ -835,9 +836,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED // Allow better error message in cases where src and dst are the same stack position. Variant ret; - getter(src, key, &ret, valid); + getter(src, key, &ret, &valid); #else - getter(src, key, dst, valid); + getter(src, key, dst, &valid); #endif #ifdef DEBUG_ENABLED if (!valid) { @@ -870,7 +871,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a int64_t int_index = *VariantInternal::get_int(index); bool oob; - getter(src, int_index, dst, oob); + getter(src, int_index, dst, &oob); #ifdef DEBUG_ENABLED if (oob) { @@ -1292,7 +1293,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(dst, argc); - constructor(*dst, (const Variant **)argptrs); + constructor(dst, (const Variant **)argptrs); ip += 3; } @@ -1749,7 +1750,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; - OPCODE(OPCODE_CALL_BUILT_IN) { + OPCODE(OPCODE_CALL_UTILITY) { CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -1757,22 +1758,80 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a int argc = _code_ptr[ip + 1]; GD_ERR_BREAK(argc < 0); - GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 2]); + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _global_names_count); + StringName function = _global_names_ptr[_code_ptr[ip + 2]]; + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + Callable::CallError err; + Variant::call_utility_function(function, dst, (const Variant **)argptrs, argc, err); + +#ifdef DEBUG_ENABLED + if (err.error != Callable::CallError::CALL_OK) { + String methodstr = function; + if (dst->get_type() == Variant::STRING) { + // Call provided error string. + err_text = "Error calling utility function '" + methodstr + "': " + String(*dst); + } else { + err_text = _get_call_error(err, "utility function '" + methodstr + "'", (const Variant **)argptrs); + } + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_UTILITY_VALIDATED) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _utilities_count); + Variant::ValidatedUtilityFunction function = _utilities_ptr[_code_ptr[ip + 2]]; + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + function(dst, (const Variant **)argptrs, argc); + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_GDSCRIPT_UTILITY) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _gds_utilities_count); + GDScriptUtilityFunctions::FunctionPtr function = _gds_utilities_ptr[_code_ptr[ip + 2]]; + Variant **argptrs = instruction_args; GET_INSTRUCTION_ARG(dst, argc); Callable::CallError err; - GDScriptFunctions::call(func, (const Variant **)argptrs, argc, *dst, err); + function(dst, (const Variant **)argptrs, argc, err); #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { - String methodstr = GDScriptFunctions::get_func_name(func); + // TODO: Add this information in debug. + String methodstr = "<unkown function>"; if (dst->get_type() == Variant::STRING) { - //call provided error string - err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst); + // Call provided error string. + err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst); } else { - err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs); + err_text = _get_call_error(err, "GDScript utility function '" + methodstr + "'", (const Variant **)argptrs); } OPCODE_BREAK; } @@ -2223,7 +2282,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a VariantInternal::initialize(counter, Variant::INT); *VariantInternal::get_int(counter) = 0; - if (!str->empty()) { + if (!str->is_empty()) { GET_INSTRUCTION_ARG(iterator, 2); VariantInternal::initialize(iterator, Variant::STRING); *VariantInternal::get_string(iterator) = str->substr(0, 1); @@ -2249,7 +2308,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a const Variant *next = dict->next(nullptr); *counter = *next; - if (!dict->empty()) { + if (!dict->is_empty()) { GET_INSTRUCTION_ARG(iterator, 2); *iterator = *next; @@ -2275,7 +2334,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a VariantInternal::initialize(counter, Variant::INT); *VariantInternal::get_int(counter) = 0; - if (!array->empty()) { + if (!array->is_empty()) { GET_INSTRUCTION_ARG(iterator, 2); *iterator = array->get(0); @@ -2298,7 +2357,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Vector<m_elem_type> *array = VariantInternal::m_get_func(container); \ VariantInternal::initialize(counter, Variant::INT); \ *VariantInternal::get_int(counter) = 0; \ - if (!array->empty()) { \ + if (!array->is_empty()) { \ GET_INSTRUCTION_ARG(iterator, 2); \ VariantInternal::initialize(iterator, Variant::m_var_ret_type); \ m_ret_type *it = VariantInternal::m_ret_get_func(iterator); \ @@ -2722,7 +2781,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(message, 1); message_str = *message; } - if (message_str.empty()) { + if (message_str.is_empty()) { err_text = "Assertion failed."; } else { err_text = "Assertion failed: " + message_str; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 668dfd4835..d2d2c23249 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -147,7 +147,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p r_symbol.script_path = path; r_symbol.children.clear(); r_symbol.name = p_class->identifier != nullptr ? String(p_class->identifier->name) : String(); - if (r_symbol.name.empty()) { + if (r_symbol.name.is_empty()) { r_symbol.name = path.get_file(); } r_symbol.kind = lsp::SymbolKind::Class; @@ -215,9 +215,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p String value_text; if (default_value.get_type() == Variant::OBJECT) { RES res = default_value; - if (res.is_valid() && !res->get_path().empty()) { + if (res.is_valid() && !res->get_path().is_empty()) { value_text = "preload(\"" + res->get_path() + "\")"; - if (symbol.documentation.empty()) { + if (symbol.documentation.is_empty()) { if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) { symbol.documentation = S->get()->class_symbol.documentation; } @@ -228,7 +228,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } else { value_text = JSON::print(default_value); } - if (!value_text.empty()) { + if (!value_text.is_empty()) { symbol.detail += " = " + value_text; } @@ -453,7 +453,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c String line = lines[i]; String first_part = line.substr(0, p_cursor.character); String last_part = line.substr(p_cursor.character + 1, lines[i].length()); - if (!p_symbol.empty()) { + if (!p_symbol.is_empty()) { String left_cursor_text; for (int c = p_cursor.character - 1; c >= 0; c--) { left_cursor_text = line.substr(c, p_cursor.character - c); @@ -589,7 +589,7 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int } const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const { - if (p_subclass.empty()) { + if (p_subclass.is_empty()) { const lsp::DocumentSymbol *const *ptr = members.getptr(p_name); if (ptr) { return *ptr; @@ -611,7 +611,7 @@ const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const } const Array &ExtendGDScriptParser::get_member_completions() { - if (member_completions.empty()) { + if (member_completions.is_empty()) { const String *name = members.next(nullptr); while (name) { const lsp::DocumentSymbol *symbol = members.get(*name); @@ -723,8 +723,8 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode } break; case ClassNode::Member::ENUM: { Dictionary enum_dict; - for (int j = 0; j < m.m_enum->values.size(); i++) { - enum_dict[m.m_enum->values[i].identifier->name] = m.m_enum->values[i].value; + for (int j = 0; j < m.m_enum->values.size(); j++) { + enum_dict[m.m_enum->values[j].identifier->name] = m.m_enum->values[j].value; } Dictionary api; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 729be237ec..cf96722c82 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -96,7 +96,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { // Response String output = GDScriptLanguageProtocol::get_singleton()->process_message(msg); - if (!output.empty()) { + if (!output.is_empty()) { res_queue.push_back(output.utf8()); } } @@ -105,7 +105,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { Error GDScriptLanguageProtocol::LSPeer::send_data() { int sent = 0; - if (!res_queue.empty()) { + if (!res_queue.is_empty()) { CharString c_res = res_queue[0]; if (res_sent < c_res.size()) { Error err = connection->put_partial_data((const uint8_t *)c_res.get_data() + res_sent, c_res.size() - res_sent - 1, sent); @@ -141,7 +141,7 @@ void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) { String GDScriptLanguageProtocol::process_message(const String &p_text) { String ret = process_string(p_text); - if (ret.empty()) { + if (ret.is_empty()) { return ret; } else { return format_output(ret); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index c6fe3169dc..abe849059f 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -147,7 +147,7 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { List<ScriptCodeCompletionOption> options; GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options); - if (!options.empty()) { + if (!options.is_empty()) { int i = 0; arr.resize(options.size()); @@ -257,7 +257,7 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { if ((item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function) && !item.label.ends_with("):")) { item.insertText = item.label + "("; - if (symbol && symbol->children.empty()) { + if (symbol && symbol->children.is_empty()) { item.insertText += ")"; } } else if (item.kind == lsp::CompletionItemKind::Event) { @@ -341,7 +341,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) { params.load(p_params); List<const lsp::DocumentSymbol *> symbols; Array arr = this->find_symbols(params, symbols); - if (arr.empty() && !symbols.empty() && !symbols.front()->get()->native_class.empty()) { // Find a native symbol + if (arr.is_empty() && !symbols.is_empty() && !symbols.front()->get()->native_class.is_empty()) { // Find a native symbol const lsp::DocumentSymbol *symbol = symbols.front()->get(); if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { String id; @@ -425,7 +425,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams & GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(p_location, list); for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { if (const lsp::DocumentSymbol *s = E->get()) { - if (!s->uri.empty()) { + if (!s->uri.is_empty()) { lsp::Location location; location.uri = s->uri; location.range = s->range; diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 60668e7b31..bccb239f28 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -80,7 +80,7 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_ if (const Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(class_name)) { const lsp::DocumentSymbol &class_symbol = E->value(); - if (p_member.empty()) { + if (p_member.is_empty()) { return &class_symbol; } else { for (int i = 0; i < class_symbol.children.size(); i++) { @@ -171,7 +171,7 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) Array GDScriptWorkspace::symbol(const Dictionary &p_params) { String query = p_params["query"]; Array arr; - if (!query.empty()) { + if (!query.is_empty()) { for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { Vector<lsp::DocumentedSymbolInformation> script_symbols; E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols); @@ -199,7 +199,7 @@ Error GDScriptWorkspace::initialize() { class_symbol.native_class = class_name; class_symbol.kind = lsp::SymbolKind::Class; class_symbol.detail = String("<Native> class ") + class_name; - if (!class_data.inherits.empty()) { + if (!class_data.inherits.is_empty()) { class_symbol.detail += " extends " + class_data.inherits; } class_symbol.documentation = class_data.brief_description + "\n" + class_data.description; @@ -263,7 +263,7 @@ Error GDScriptWorkspace::initialize() { symbol_arg.kind = lsp::SymbolKind::Variable; symbol_arg.detail = arg.type; - if (!arg_default_value_started && !arg.default_value.empty()) { + if (!arg_default_value_started && !arg.default_value.is_empty()) { arg_default_value_started = true; } String arg_str = arg.name + ": " + arg.type; @@ -278,11 +278,11 @@ Error GDScriptWorkspace::initialize() { symbol.children.push_back(symbol_arg); } if (data.qualifiers.find("vararg") != -1) { - params += params.empty() ? "..." : ", ..."; + params += params.is_empty() ? "..." : ", ..."; } String return_type = data.return_type; - if (return_type.empty()) { + if (return_type.is_empty()) { return_type = "void"; } symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type; @@ -448,13 +448,13 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } lsp::Position pos = p_doc_pos.position; - if (symbol_identifier.empty()) { + if (symbol_identifier.is_empty()) { Vector2i offset; symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); pos.character += offset.y; } - if (!symbol_identifier.empty()) { + if (!symbol_identifier.is_empty()) { if (ScriptServer::is_global_class(symbol_identifier)) { String class_path = ScriptServer::get_global_class_path(symbol_identifier); symbol = get_script_symbol(class_path); @@ -474,7 +474,7 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } else { String member = ret.class_member; - if (member.empty() && symbol_identifier != ret.class_name) { + if (member.is_empty() && symbol_identifier != ret.class_name) { member = symbol_identifier; } symbol = get_native_symbol(ret.class_name, member); @@ -529,7 +529,7 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) { if (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(p_params.native_class)) { const lsp::DocumentSymbol &symbol = E->get(); - if (p_params.symbol_name.empty() || p_params.symbol_name == symbol.name) { + if (p_params.symbol_name.is_empty() || p_params.symbol_name == symbol.name) { return &symbol; } diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 1029c53bbf..e844a402e2 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -687,7 +687,7 @@ struct Diagnostic { dict["severity"] = severity; dict["message"] = message; dict["source"] = source; - if (!relatedInformation.empty()) { + if (!relatedInformation.is_empty()) { Array arr; arr.resize(relatedInformation.size()); for (int i = 0; i < relatedInformation.size(); i++) { @@ -1191,7 +1191,7 @@ struct DocumentSymbol { void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const { DocumentedSymbolInformation si; - if (p_join_name && !p_container.empty()) { + if (p_join_name && !p_container.is_empty()) { si.name = p_container + ">" + name; } else { si.name = name; diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 6c2af66c65..0c4996e9bb 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -38,6 +38,7 @@ #include "gdscript_analyzer.h" #include "gdscript_cache.h" #include "gdscript_tokenizer.h" +#include "gdscript_utility_functions.h" #ifdef TESTS_ENABLED #include "tests/test_gdscript.h" @@ -130,6 +131,8 @@ void register_gdscript_types() { gdscript_translation_parser_plugin.instance(); EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD); #endif // TOOLS_ENABLED + + GDScriptUtilityFunctions::register_functions(); } void unregister_gdscript_types() { @@ -156,6 +159,7 @@ void unregister_gdscript_types() { GDScriptParser::cleanup(); GDScriptAnalyzer::cleanup(); + GDScriptUtilityFunctions::unregister_functions(); } #ifdef TESTS_ENABLED diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index 643c2f10a2..29efa33e70 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -239,7 +239,7 @@ void init_autoloads() { void test(TestType p_type) { List<String> cmdlargs = OS::get_singleton()->get_cmdline_args(); - if (cmdlargs.empty()) { + if (cmdlargs.is_empty()) { return; } diff --git a/modules/glslang/SCsub b/modules/glslang/SCsub index 58c033c75d..182272ffc7 100644 --- a/modules/glslang/SCsub +++ b/modules/glslang/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_glslang = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + if env["builtin_glslang"]: thirdparty_dir = "#thirdparty/glslang/" thirdparty_sources = [ @@ -70,7 +73,16 @@ if env["builtin_glslang"]: env_thirdparty = env_glslang.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + + +# Godot source files + +module_obj = [] + +env_glslang.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -# Godot's own source files -env_glslang.add_source_files(env.modules_sources, "*.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/gltf/SCsub b/modules/gltf/SCsub new file mode 100644 index 0000000000..5d03ee8361 --- /dev/null +++ b/modules/gltf/SCsub @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_gltf = env_modules.Clone() +env_gltf.Prepend(CPPPATH=["."]) + +# Godot's own source files +env_gltf.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/assimp/config.py b/modules/gltf/config.py index 53b8f2f2e3..1505a456d7 100644 --- a/modules/assimp/config.py +++ b/modules/gltf/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return env["tools"] + return env["tools"] and not env["disable_3d"] def configure(env): diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp new file mode 100644 index 0000000000..894d7150b5 --- /dev/null +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* editor_scene_exporter_gltf_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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/config/project_settings.h" +#include "core/object/object.h" +#include "core/templates/vector.h" +#include "editor/editor_file_system.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/gui/check_box.h" +#include "scene/main/node.h" + +#include "editor/editor_node.h" + +String SceneExporterGLTFPlugin::get_name() const { + return "ConvertGLTF2"; +} + +bool SceneExporterGLTFPlugin::has_main_screen() const { + return false; +} + +SceneExporterGLTFPlugin::SceneExporterGLTFPlugin(EditorNode *p_node) { + editor = p_node; + convert_gltf2.instance(); + file_export_lib = memnew(EditorFileDialog); + editor->get_gui_base()->add_child(file_export_lib); + file_export_lib->connect("file_selected", callable_mp(this, &SceneExporterGLTFPlugin::_gltf2_dialog_action)); + file_export_lib->set_title(TTR("Export Library")); + file_export_lib->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file_export_lib->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file_export_lib->clear_filters(); + file_export_lib->add_filter("*.glb"); + file_export_lib->add_filter("*.gltf"); + file_export_lib->set_title(TTR("Export Mesh GLTF2")); + String gltf_scene_name = TTR("Export GLTF..."); + add_tool_menu_item(gltf_scene_name, callable_mp(this, &SceneExporterGLTFPlugin::convert_scene_to_gltf2)); +} + +void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) { + Node *root = editor->get_tree()->get_edited_scene_root(); + if (!root) { + editor->show_accept(TTR("This operation can't be done without a scene."), TTR("OK")); + return; + } + List<String> deps; + convert_gltf2->save_scene(root, p_file, p_file, 0, 1000.0f, &deps); + EditorFileSystem::get_singleton()->scan_changes(); +} + +void SceneExporterGLTFPlugin::convert_scene_to_gltf2() { + Node *root = editor->get_tree()->get_edited_scene_root(); + if (!root) { + editor->show_accept(TTR("This operation can't be done without a scene."), TTR("OK")); + return; + } + String filename = String(root->get_filename().get_file().get_basename()); + if (filename.is_empty()) { + filename = root->get_name(); + } + file_export_lib->set_current_file(filename + String(".gltf")); + file_export_lib->popup_centered_ratio(); +} diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor_scene_exporter_gltf_plugin.h new file mode 100644 index 0000000000..1a8910d866 --- /dev/null +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* editor_scene_exporter_gltf_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_SCENE_EXPORTER_GLTF_PLUGIN_H +#define EDITOR_SCENE_EXPORTER_GLTF_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "editor_scene_importer_gltf.h" + +class SceneExporterGLTFPlugin : public EditorPlugin { + GDCLASS(SceneExporterGLTFPlugin, EditorPlugin); + + Ref<PackedSceneGLTF> convert_gltf2; + EditorNode *editor = nullptr; + EditorFileDialog *file_export_lib = nullptr; + void _gltf2_dialog_action(String p_file); + void convert_scene_to_gltf2(); + +public: + virtual String get_name() const override; + bool has_main_screen() const override; + SceneExporterGLTFPlugin(class EditorNode *p_node); +}; + +#endif // EDITOR_SCENE_EXPORTER_GLTF_PLUGIN_H diff --git a/modules/gltf/editor_scene_importer_gltf.cpp b/modules/gltf/editor_scene_importer_gltf.cpp new file mode 100644 index 0000000000..51cb3a6d2e --- /dev/null +++ b/modules/gltf/editor_scene_importer_gltf.cpp @@ -0,0 +1,180 @@ +/*************************************************************************/ +/* editor_scene_importer_gltf.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/crypto/crypto_core.h" +#include "core/io/json.h" +#include "core/math/disjoint_set.h" +#include "core/math/math_defs.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "editor/import/resource_importer_scene.h" +#include "modules/gltf/gltf_state.h" +#include "modules/regex/regex.h" +#include "scene/3d/bone_attachment_3d.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/packed_scene.h" +#include "scene/resources/surface_tool.h" + +#include "modules/gltf/editor_scene_importer_gltf.h" + +uint32_t EditorSceneImporterGLTF::get_import_flags() const { + return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; +} + +void EditorSceneImporterGLTF::get_extensions(List<String> *r_extensions) const { + r_extensions->push_back("gltf"); + r_extensions->push_back("glb"); +} + +Node *EditorSceneImporterGLTF::import_scene(const String &p_path, + uint32_t p_flags, int p_bake_fps, + List<String> *r_missing_deps, + Error *r_err) { + Ref<PackedSceneGLTF> importer; + importer.instance(); + return importer->import_scene(p_path, p_flags, p_bake_fps, r_missing_deps, r_err, Ref<GLTFState>()); +} + +Ref<Animation> EditorSceneImporterGLTF::import_animation(const String &p_path, + uint32_t p_flags, + int p_bake_fps) { + return Ref<Animation>(); +} + +void PackedSceneGLTF::_bind_methods() { + ClassDB::bind_method( + D_METHOD("export_gltf", "node", "path", "flags", "bake_fps"), + &PackedSceneGLTF::export_gltf, DEFVAL(0), DEFVAL(1000.0f)); + ClassDB::bind_method(D_METHOD("pack_gltf", "path", "flags", "bake_fps", "state"), + &PackedSceneGLTF::pack_gltf, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref<GLTFState>())); + ClassDB::bind_method(D_METHOD("import_gltf_scene", "path", "flags", "bake_fps", "state"), + &PackedSceneGLTF::import_gltf_scene, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref<GLTFState>())); +} +Node *PackedSceneGLTF::import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref<GLTFState> r_state) { + Error err = FAILED; + List<String> deps; + return import_scene(p_path, p_flags, p_bake_fps, &deps, &err, r_state); +} + +Node *PackedSceneGLTF::import_scene(const String &p_path, uint32_t p_flags, + int p_bake_fps, + List<String> *r_missing_deps, + Error *r_err, + Ref<GLTFState> r_state) { + if (r_state == Ref<GLTFState>()) { + r_state.instance(); + } + r_state->use_named_skin_binds = + p_flags & EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; + + Ref<GLTFDocument> gltf_document; + gltf_document.instance(); + Error err = gltf_document->parse(r_state, p_path); + *r_err = err; + ERR_FAIL_COND_V(err != Error::OK, nullptr); + + Node3D *root = memnew(Node3D); + for (int32_t root_i = 0; root_i < r_state->root_nodes.size(); root_i++) { + gltf_document->_generate_scene_node(r_state, root, root, r_state->root_nodes[root_i]); + } + gltf_document->_process_mesh_instances(r_state, root); + if (r_state->animations.size()) { + AnimationPlayer *ap = memnew(AnimationPlayer); + root->add_child(ap); + ap->set_owner(root); + for (int i = 0; i < r_state->animations.size(); i++) { + gltf_document->_import_animation(r_state, ap, i, p_bake_fps); + } + } + + return cast_to<Node3D>(root); +} + +void PackedSceneGLTF::pack_gltf(String p_path, int32_t p_flags, + real_t p_bake_fps, Ref<GLTFState> r_state) { + Error err = FAILED; + List<String> deps; + Node *root = import_scene(p_path, p_flags, p_bake_fps, &deps, &err, r_state); + ERR_FAIL_COND(err != OK); + pack(root); +} + +void PackedSceneGLTF::save_scene(Node *p_node, const String &p_path, + const String &p_src_path, uint32_t p_flags, + int p_bake_fps, List<String> *r_missing_deps, + Error *r_err) { + Error err = FAILED; + if (r_err) { + *r_err = err; + } + Ref<GLTFDocument> gltf_document; + gltf_document.instance(); + Ref<GLTFState> state; + state.instance(); + err = gltf_document->serialize(state, p_node, p_path); + if (r_err) { + *r_err = err; + } +} + +void PackedSceneGLTF::_build_parent_hierachy(Ref<GLTFState> state) { + // build the hierarchy + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; + ERR_FAIL_INDEX(child_i, state->nodes.size()); + if (state->nodes.write[child_i]->parent != -1) { + continue; + } + state->nodes.write[child_i]->parent = node_i; + } + } +} + +Error PackedSceneGLTF::export_gltf(Node *p_root, String p_path, + int32_t p_flags, + real_t p_bake_fps) { + ERR_FAIL_COND_V(!p_root, FAILED); + List<String> deps; + Error err; + String path = p_path; + int32_t flags = p_flags; + real_t baked_fps = p_bake_fps; + Ref<PackedSceneGLTF> exporter; + exporter.instance(); + exporter->save_scene(p_root, path, "", flags, baked_fps, &deps, &err); + int32_t error_code = err; + if (error_code != 0) { + return Error(error_code); + } + return OK; +} diff --git a/modules/gltf/editor_scene_importer_gltf.h b/modules/gltf/editor_scene_importer_gltf.h new file mode 100644 index 0000000000..3da987493c --- /dev/null +++ b/modules/gltf/editor_scene_importer_gltf.h @@ -0,0 +1,96 @@ +/*************************************************************************/ +/* editor_scene_importer_gltf.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_SCENE_IMPORTER_GLTF_H +#define EDITOR_SCENE_IMPORTER_GLTF_H + +#include "core/config/project_settings.h" +#include "core/io/json.h" +#include "core/object/object.h" +#include "core/templates/vector.h" +#include "editor/import/resource_importer_scene.h" +#include "modules/csg/csg_shape.h" +#include "modules/gridmap/grid_map.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/multimesh_instance_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/animation/animation_player.h" +#include "scene/gui/check_box.h" +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" +#include "scene/resources/surface_tool.h" + +#include "gltf_document.h" +#include "gltf_state.h" + +class AnimationPlayer; +class BoneAttachment; +class EditorSceneImporterMeshNode3D; + +#ifdef TOOLS_ENABLED +class EditorSceneImporterGLTF : public EditorSceneImporter { + GDCLASS(EditorSceneImporterGLTF, EditorSceneImporter); + +public: + virtual uint32_t get_import_flags() const override; + virtual void get_extensions(List<String> *r_extensions) const override; + virtual Node *import_scene(const String &p_path, uint32_t p_flags, + int p_bake_fps, + List<String> *r_missing_deps = NULL, + Error *r_err = NULL) override; + virtual Ref<Animation> import_animation(const String &p_path, + uint32_t p_flags, int p_bake_fps) override; +}; +#endif + +class PackedSceneGLTF : public PackedScene { + GDCLASS(PackedSceneGLTF, PackedScene); + +protected: + static void _bind_methods(); + +public: + virtual void save_scene(Node *p_node, const String &p_path, const String &p_src_path, + uint32_t p_flags, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err = NULL); + virtual void _build_parent_hierachy(Ref<GLTFState> state); + virtual Error export_gltf(Node *p_root, String p_path, int32_t p_flags = 0, + real_t p_bake_fps = 1000.0f); + virtual Node *import_scene(const String &p_path, uint32_t p_flags, + int p_bake_fps, + List<String> *r_missing_deps, + Error *r_err, + Ref<GLTFState> r_state); + virtual Node *import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref<GLTFState> r_state = Ref<GLTFState>()); + virtual void pack_gltf(String p_path, int32_t p_flags = 0, + real_t p_bake_fps = 1000.0f, Ref<GLTFState> r_state = Ref<GLTFState>()); +}; +#endif // EDITOR_SCENE_IMPORTER_GLTF_H diff --git a/modules/gltf/gltf_accessor.cpp b/modules/gltf/gltf_accessor.cpp new file mode 100644 index 0000000000..9267c58598 --- /dev/null +++ b/modules/gltf/gltf_accessor.cpp @@ -0,0 +1,189 @@ +/*************************************************************************/ +/* gltf_accessor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_accessor.h" + +void GLTFAccessor::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_buffer_view"), &GLTFAccessor::get_buffer_view); + ClassDB::bind_method(D_METHOD("set_buffer_view", "buffer_view"), &GLTFAccessor::set_buffer_view); + ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFAccessor::get_byte_offset); + ClassDB::bind_method(D_METHOD("set_byte_offset", "byte_offset"), &GLTFAccessor::set_byte_offset); + ClassDB::bind_method(D_METHOD("get_component_type"), &GLTFAccessor::get_component_type); + ClassDB::bind_method(D_METHOD("set_component_type", "component_type"), &GLTFAccessor::set_component_type); + ClassDB::bind_method(D_METHOD("get_normalized"), &GLTFAccessor::get_normalized); + ClassDB::bind_method(D_METHOD("set_normalized", "normalized"), &GLTFAccessor::set_normalized); + ClassDB::bind_method(D_METHOD("get_count"), &GLTFAccessor::get_count); + ClassDB::bind_method(D_METHOD("set_count", "count"), &GLTFAccessor::set_count); + ClassDB::bind_method(D_METHOD("get_type"), &GLTFAccessor::get_type); + ClassDB::bind_method(D_METHOD("set_type", "type"), &GLTFAccessor::set_type); + ClassDB::bind_method(D_METHOD("get_min"), &GLTFAccessor::get_min); + ClassDB::bind_method(D_METHOD("set_min", "min"), &GLTFAccessor::set_min); + ClassDB::bind_method(D_METHOD("get_max"), &GLTFAccessor::get_max); + ClassDB::bind_method(D_METHOD("set_max", "max"), &GLTFAccessor::set_max); + ClassDB::bind_method(D_METHOD("get_sparse_count"), &GLTFAccessor::get_sparse_count); + ClassDB::bind_method(D_METHOD("set_sparse_count", "sparse_count"), &GLTFAccessor::set_sparse_count); + ClassDB::bind_method(D_METHOD("get_sparse_indices_buffer_view"), &GLTFAccessor::get_sparse_indices_buffer_view); + ClassDB::bind_method(D_METHOD("set_sparse_indices_buffer_view", "sparse_indices_buffer_view"), &GLTFAccessor::set_sparse_indices_buffer_view); + ClassDB::bind_method(D_METHOD("get_sparse_indices_byte_offset"), &GLTFAccessor::get_sparse_indices_byte_offset); + ClassDB::bind_method(D_METHOD("set_sparse_indices_byte_offset", "sparse_indices_byte_offset"), &GLTFAccessor::set_sparse_indices_byte_offset); + ClassDB::bind_method(D_METHOD("get_sparse_indices_component_type"), &GLTFAccessor::get_sparse_indices_component_type); + ClassDB::bind_method(D_METHOD("set_sparse_indices_component_type", "sparse_indices_component_type"), &GLTFAccessor::set_sparse_indices_component_type); + ClassDB::bind_method(D_METHOD("get_sparse_values_buffer_view"), &GLTFAccessor::get_sparse_values_buffer_view); + ClassDB::bind_method(D_METHOD("set_sparse_values_buffer_view", "sparse_values_buffer_view"), &GLTFAccessor::set_sparse_values_buffer_view); + ClassDB::bind_method(D_METHOD("get_sparse_values_byte_offset"), &GLTFAccessor::get_sparse_values_byte_offset); + ClassDB::bind_method(D_METHOD("set_sparse_values_byte_offset", "sparse_values_byte_offset"), &GLTFAccessor::set_sparse_values_byte_offset); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "buffer_view"), "set_buffer_view", "get_buffer_view"); // GLTFBufferViewIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "byte_offset"), "set_byte_offset", "get_byte_offset"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "component_type"), "set_component_type", "get_component_type"); // int + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalized"), "set_normalized", "get_normalized"); // bool + ADD_PROPERTY(PropertyInfo(Variant::INT, "count"), "set_count", "get_count"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_type", "get_type"); // GLTFDocument::GLTFType + ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT64_ARRAY, "min"), "set_min", "get_min"); // Vector<real_t> + ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT64_ARRAY, "max"), "set_max", "get_max"); // Vector<real_t> + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_count"), "set_sparse_count", "get_sparse_count"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_indices_buffer_view"), "set_sparse_indices_buffer_view", "get_sparse_indices_buffer_view"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_indices_byte_offset"), "set_sparse_indices_byte_offset", "get_sparse_indices_byte_offset"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_indices_component_type"), "set_sparse_indices_component_type", "get_sparse_indices_component_type"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_values_buffer_view"), "set_sparse_values_buffer_view", "get_sparse_values_buffer_view"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_values_byte_offset"), "set_sparse_values_byte_offset", "get_sparse_values_byte_offset"); // int +} + +GLTFBufferViewIndex GLTFAccessor::get_buffer_view() { + return buffer_view; +} + +void GLTFAccessor::set_buffer_view(GLTFBufferViewIndex p_buffer_view) { + buffer_view = p_buffer_view; +} + +int GLTFAccessor::get_byte_offset() { + return byte_offset; +} + +void GLTFAccessor::set_byte_offset(int p_byte_offset) { + byte_offset = p_byte_offset; +} + +int GLTFAccessor::get_component_type() { + return component_type; +} + +void GLTFAccessor::set_component_type(int p_component_type) { + component_type = p_component_type; +} + +bool GLTFAccessor::get_normalized() { + return normalized; +} + +void GLTFAccessor::set_normalized(bool p_normalized) { + normalized = p_normalized; +} + +int GLTFAccessor::get_count() { + return count; +} + +void GLTFAccessor::set_count(int p_count) { + count = p_count; +} + +int GLTFAccessor::get_type() { + return (int)type; +} + +void GLTFAccessor::set_type(int p_type) { + type = (GLTFDocument::GLTFType)p_type; // TODO: Register enum +} + +Vector<double> GLTFAccessor::get_min() { + return min; +} + +void GLTFAccessor::set_min(Vector<double> p_min) { + min = p_min; +} + +Vector<double> GLTFAccessor::get_max() { + return max; +} + +void GLTFAccessor::set_max(Vector<double> p_max) { + max = p_max; +} + +int GLTFAccessor::get_sparse_count() { + return sparse_count; +} + +void GLTFAccessor::set_sparse_count(int p_sparse_count) { + sparse_count = p_sparse_count; +} + +int GLTFAccessor::get_sparse_indices_buffer_view() { + return sparse_indices_buffer_view; +} + +void GLTFAccessor::set_sparse_indices_buffer_view(int p_sparse_indices_buffer_view) { + sparse_indices_buffer_view = p_sparse_indices_buffer_view; +} + +int GLTFAccessor::get_sparse_indices_byte_offset() { + return sparse_indices_byte_offset; +} + +void GLTFAccessor::set_sparse_indices_byte_offset(int p_sparse_indices_byte_offset) { + sparse_indices_byte_offset = p_sparse_indices_byte_offset; +} + +int GLTFAccessor::get_sparse_indices_component_type() { + return sparse_indices_component_type; +} + +void GLTFAccessor::set_sparse_indices_component_type(int p_sparse_indices_component_type) { + sparse_indices_component_type = p_sparse_indices_component_type; +} + +int GLTFAccessor::get_sparse_values_buffer_view() { + return sparse_values_buffer_view; +} + +void GLTFAccessor::set_sparse_values_buffer_view(int p_sparse_values_buffer_view) { + sparse_values_buffer_view = p_sparse_values_buffer_view; +} + +int GLTFAccessor::get_sparse_values_byte_offset() { + return sparse_values_byte_offset; +} + +void GLTFAccessor::set_sparse_values_byte_offset(int p_sparse_values_byte_offset) { + sparse_values_byte_offset = p_sparse_values_byte_offset; +} diff --git a/modules/gltf/gltf_accessor.h b/modules/gltf/gltf_accessor.h new file mode 100644 index 0000000000..7317928848 --- /dev/null +++ b/modules/gltf/gltf_accessor.h @@ -0,0 +1,104 @@ +/*************************************************************************/ +/* gltf_accessor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_ACCESSOR_H +#define GLTF_ACCESSOR_H + +#include "core/io/resource.h" +#include "gltf_document.h" + +struct GLTFAccessor : public Resource { + GDCLASS(GLTFAccessor, Resource); + friend class GLTFDocument; + +private: + GLTFBufferViewIndex buffer_view = 0; + int byte_offset = 0; + int component_type = 0; + bool normalized = false; + int count = 0; + GLTFDocument::GLTFType + type = GLTFDocument::TYPE_SCALAR; + Vector<double> min; + Vector<double> max; + int sparse_count = 0; + int sparse_indices_buffer_view = 0; + int sparse_indices_byte_offset = 0; + int sparse_indices_component_type = 0; + int sparse_values_buffer_view = 0; + int sparse_values_byte_offset = 0; + +protected: + static void _bind_methods(); + +public: + GLTFBufferViewIndex get_buffer_view(); + void set_buffer_view(GLTFBufferViewIndex p_buffer_view); + + int get_byte_offset(); + void set_byte_offset(int p_byte_offset); + + int get_component_type(); + void set_component_type(int p_component_type); + + bool get_normalized(); + void set_normalized(bool p_normalized); + + int get_count(); + void set_count(int p_count); + + int get_type(); + void set_type(int p_type); + + Vector<double> get_min(); + void set_min(Vector<double> p_min); + + Vector<double> get_max(); + void set_max(Vector<double> p_max); + + int get_sparse_count(); + void set_sparse_count(int p_sparse_count); + + int get_sparse_indices_buffer_view(); + void set_sparse_indices_buffer_view(int p_sparse_indices_buffer_view); + + int get_sparse_indices_byte_offset(); + void set_sparse_indices_byte_offset(int p_sparse_indices_byte_offset); + + int get_sparse_indices_component_type(); + void set_sparse_indices_component_type(int p_sparse_indices_component_type); + + int get_sparse_values_buffer_view(); + void set_sparse_values_buffer_view(int p_sparse_values_buffer_view); + + int get_sparse_values_byte_offset(); + void set_sparse_values_byte_offset(int p_sparse_values_byte_offset); +}; +#endif // GLTF_ACCESSOR_H diff --git a/modules/gltf/gltf_animation.cpp b/modules/gltf/gltf_animation.cpp new file mode 100644 index 0000000000..85ba117627 --- /dev/null +++ b/modules/gltf/gltf_animation.cpp @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* gltf_animation.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_animation.h" + +void GLTFAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_loop"), &GLTFAnimation::get_loop); + ClassDB::bind_method(D_METHOD("set_loop", "loop"), &GLTFAnimation::set_loop); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "get_loop"); // bool +} + +bool GLTFAnimation::get_loop() const { + return loop; +} + +void GLTFAnimation::set_loop(bool p_val) { + loop = p_val; +} + +Map<int, GLTFAnimation::Track> &GLTFAnimation::get_tracks() { + return tracks; +} + +GLTFAnimation::GLTFAnimation() { +} diff --git a/modules/gltf/gltf_animation.h b/modules/gltf/gltf_animation.h new file mode 100644 index 0000000000..37fd1a2007 --- /dev/null +++ b/modules/gltf/gltf_animation.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* gltf_animation.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_ANIMATION_H +#define GLTF_ANIMATION_H + +#include "core/io/resource.h" + +class GLTFAnimation : public Resource { + GDCLASS(GLTFAnimation, Resource); + +protected: + static void _bind_methods(); + +public: + enum Interpolation { + INTERP_LINEAR, + INTERP_STEP, + INTERP_CATMULLROMSPLINE, + INTERP_CUBIC_SPLINE, + }; + + template <class T> + struct Channel { + Interpolation interpolation; + Vector<float> times; + Vector<T> values; + }; + + struct Track { + Channel<Vector3> translation_track; + Channel<Quat> rotation_track; + Channel<Vector3> scale_track; + Vector<Channel<float>> weight_tracks; + }; + +public: + bool get_loop() const; + void set_loop(bool p_val); + Map<int, GLTFAnimation::Track> &get_tracks(); + GLTFAnimation(); + +private: + bool loop = false; + Map<int, Track> tracks; +}; +#endif // GLTF_ANIMATION_H diff --git a/modules/gltf/gltf_buffer_view.cpp b/modules/gltf/gltf_buffer_view.cpp new file mode 100644 index 0000000000..edfdad40bb --- /dev/null +++ b/modules/gltf/gltf_buffer_view.cpp @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* gltf_buffer_view.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_buffer_view.h" + +void GLTFBufferView::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_buffer"), &GLTFBufferView::get_buffer); + ClassDB::bind_method(D_METHOD("set_buffer", "buffer"), &GLTFBufferView::set_buffer); + ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFBufferView::get_byte_offset); + ClassDB::bind_method(D_METHOD("set_byte_offset", "byte_offset"), &GLTFBufferView::set_byte_offset); + ClassDB::bind_method(D_METHOD("get_byte_length"), &GLTFBufferView::get_byte_length); + ClassDB::bind_method(D_METHOD("set_byte_length", "byte_length"), &GLTFBufferView::set_byte_length); + ClassDB::bind_method(D_METHOD("get_byte_stride"), &GLTFBufferView::get_byte_stride); + ClassDB::bind_method(D_METHOD("set_byte_stride", "byte_stride"), &GLTFBufferView::set_byte_stride); + ClassDB::bind_method(D_METHOD("get_indices"), &GLTFBufferView::get_indices); + ClassDB::bind_method(D_METHOD("set_indices", "indices"), &GLTFBufferView::set_indices); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "buffer"), "set_buffer", "get_buffer"); // GLTFBufferIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "byte_offset"), "set_byte_offset", "get_byte_offset"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "byte_length"), "set_byte_length", "get_byte_length"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "byte_stride"), "set_byte_stride", "get_byte_stride"); // int + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indices"), "set_indices", "get_indices"); // bool +} + +GLTFBufferIndex GLTFBufferView::get_buffer() { + return buffer; +} + +void GLTFBufferView::set_buffer(GLTFBufferIndex p_buffer) { + buffer = p_buffer; +} + +int GLTFBufferView::get_byte_offset() { + return byte_offset; +} + +void GLTFBufferView::set_byte_offset(int p_byte_offset) { + byte_offset = p_byte_offset; +} + +int GLTFBufferView::get_byte_length() { + return byte_length; +} + +void GLTFBufferView::set_byte_length(int p_byte_length) { + byte_length = p_byte_length; +} + +int GLTFBufferView::get_byte_stride() { + return byte_stride; +} + +void GLTFBufferView::set_byte_stride(int p_byte_stride) { + byte_stride = p_byte_stride; +} + +bool GLTFBufferView::get_indices() { + return indices; +} + +void GLTFBufferView::set_indices(bool p_indices) { + indices = p_indices; +} diff --git a/modules/gltf/gltf_buffer_view.h b/modules/gltf/gltf_buffer_view.h new file mode 100644 index 0000000000..0b0c8af173 --- /dev/null +++ b/modules/gltf/gltf_buffer_view.h @@ -0,0 +1,68 @@ +/*************************************************************************/ +/* gltf_buffer_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_BUFFER_VIEW_H +#define GLTF_BUFFER_VIEW_H + +#include "core/io/resource.h" +#include "gltf_document.h" + +class GLTFBufferView : public Resource { + GDCLASS(GLTFBufferView, Resource); + friend class GLTFDocument; + +private: + GLTFBufferIndex buffer = -1; + int byte_offset = 0; + int byte_length = 0; + int byte_stride = -1; + bool indices = false; + +protected: + static void _bind_methods(); + +public: + GLTFBufferIndex get_buffer(); + void set_buffer(GLTFBufferIndex p_buffer); + + int get_byte_offset(); + void set_byte_offset(int p_byte_offset); + + int get_byte_length(); + void set_byte_length(int p_byte_length); + + int get_byte_stride(); + void set_byte_stride(int p_byte_stride); + + bool get_indices(); + void set_indices(bool p_indices); + // matrices need to be transformed to this +}; +#endif // GLTF_BUFFER_VIEW_H diff --git a/modules/gltf/gltf_camera.cpp b/modules/gltf/gltf_camera.cpp new file mode 100644 index 0000000000..e5cfc1cbb1 --- /dev/null +++ b/modules/gltf/gltf_camera.cpp @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* gltf_camera.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_camera.h" + +void GLTFCamera::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_perspective"), &GLTFCamera::get_perspective); + ClassDB::bind_method(D_METHOD("set_perspective", "perspective"), &GLTFCamera::set_perspective); + ClassDB::bind_method(D_METHOD("get_fov_size"), &GLTFCamera::get_fov_size); + ClassDB::bind_method(D_METHOD("set_fov_size", "fov_size"), &GLTFCamera::set_fov_size); + ClassDB::bind_method(D_METHOD("get_zfar"), &GLTFCamera::get_zfar); + ClassDB::bind_method(D_METHOD("set_zfar", "zfar"), &GLTFCamera::set_zfar); + ClassDB::bind_method(D_METHOD("get_znear"), &GLTFCamera::get_znear); + ClassDB::bind_method(D_METHOD("set_znear", "znear"), &GLTFCamera::set_znear); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "perspective"), "set_perspective", "get_perspective"); // bool + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fov_size"), "set_fov_size", "get_fov_size"); // float + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zfar"), "set_zfar", "get_zfar"); // float + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "znear"), "set_znear", "get_znear"); // float +} diff --git a/modules/gltf/gltf_camera.h b/modules/gltf/gltf_camera.h new file mode 100644 index 0000000000..7e167af7be --- /dev/null +++ b/modules/gltf/gltf_camera.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* gltf_camera.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_CAMERA_H +#define GLTF_CAMERA_H + +#include "core/io/resource.h" + +class GLTFCamera : public Resource { + GDCLASS(GLTFCamera, Resource); + +private: + bool perspective = true; + float fov_size = 75; + float zfar = 4000; + float znear = 0.05; + +protected: + static void _bind_methods(); + +public: + bool get_perspective() const { return perspective; } + void set_perspective(bool p_val) { perspective = p_val; } + float get_fov_size() const { return fov_size; } + void set_fov_size(float p_val) { fov_size = p_val; } + float get_zfar() const { return zfar; } + void set_zfar(float p_val) { zfar = p_val; } + float get_znear() const { return znear; } + void set_znear(float p_val) { znear = p_val; } +}; +#endif // GLTF_CAMERA_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp new file mode 100644 index 0000000000..5eaa1741ea --- /dev/null +++ b/modules/gltf/gltf_document.cpp @@ -0,0 +1,6403 @@ +/*************************************************************************/ +/* gltf_document.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "gltf_accessor.h" +#include "gltf_animation.h" +#include "gltf_camera.h" +#include "gltf_light.h" +#include "gltf_mesh.h" +#include "gltf_node.h" +#include "gltf_skeleton.h" +#include "gltf_skin.h" +#include "gltf_spec_gloss.h" +#include "gltf_state.h" +#include "gltf_texture.h" + +#include <stdio.h> +#include <stdlib.h> + +#include "core/core_bind.h" +#include "core/crypto/crypto_core.h" +#include "core/io/json.h" +#include "core/math/disjoint_set.h" +#include "core/os/file_access.h" +#include "core/variant/typed_array.h" +#include "core/version.h" +#include "core/version_hash.gen.h" +#include "drivers/png/png_driver_common.h" +#include "editor/import/resource_importer_scene.h" +#include "modules/csg/csg_shape.h" +#include "modules/gridmap/grid_map.h" +#include "modules/regex/regex.h" +#include "scene/2d/node_2d.h" +#include "scene/3d/bone_attachment_3d.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/multimesh_instance_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/surface_tool.h" +#include <limits> + +Error GLTFDocument::serialize(Ref<GLTFState> state, Node *p_root, const String &p_path) { + uint64_t begin_time = OS::get_singleton()->get_ticks_usec(); + + _convert_scene_node(state, p_root, p_root, -1, -1); + if (!state->buffers.size()) { + state->buffers.push_back(Vector<uint8_t>()); + } + + /* STEP 1 CONVERT MESH INSTANCES */ + _convert_mesh_instances(state); + + /* STEP 2 SERIALIZE CAMERAS */ + Error err = _serialize_cameras(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 3 CREATE SKINS */ + err = _serialize_skins(state); + if (err != OK) { + return Error::FAILED; + } + /* STEP 4 CREATE BONE ATTACHMENTS */ + err = _serialize_bone_attachment(state); + if (err != OK) { + return Error::FAILED; + } + /* STEP 5 SERIALIZE MESHES (we have enough info now) */ + err = _serialize_meshes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 6 SERIALIZE TEXTURES */ + err = _serialize_materials(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 7 SERIALIZE IMAGES */ + err = _serialize_images(state, p_path); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 8 SERIALIZE TEXTURES */ + err = _serialize_textures(state); + if (err != OK) { + return Error::FAILED; + } + + // /* STEP 9 SERIALIZE ANIMATIONS */ + err = _serialize_animations(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 10 SERIALIZE ACCESSORS */ + err = _encode_accessors(state); + if (err != OK) { + return Error::FAILED; + } + + for (GLTFBufferViewIndex i = 0; i < state->buffer_views.size(); i++) { + state->buffer_views.write[i]->buffer = 0; + } + + /* STEP 11 SERIALIZE BUFFER VIEWS */ + err = _encode_buffer_views(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 12 SERIALIZE NODES */ + err = _serialize_nodes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 13 SERIALIZE SCENE */ + err = _serialize_scenes(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 14 SERIALIZE SCENE */ + err = _serialize_lights(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 15 SERIALIZE EXTENSIONS */ + err = _serialize_extensions(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 16 SERIALIZE VERSION */ + err = _serialize_version(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 17 SERIALIZE FILE */ + err = _serialize_file(state, p_path); + if (err != OK) { + return Error::FAILED; + } + uint64_t elapsed = OS::get_singleton()->get_ticks_usec() - begin_time; + float elapsed_sec = double(elapsed) / 1000000.0; + elapsed_sec = Math::stepify(elapsed_sec, 0.01f); + print_line("glTF: Export time elapsed seconds " + rtos(elapsed_sec).pad_decimals(2)); + + return OK; +} + +Error GLTFDocument::_serialize_extensions(Ref<GLTFState> state) const { + const String texture_transform = "KHR_texture_transform"; + const String punctual_lights = "KHR_lights_punctual"; + Array extensions_used; + extensions_used.push_back(punctual_lights); + extensions_used.push_back(texture_transform); + state->json["extensionsUsed"] = extensions_used; + Array extensions_required; + extensions_required.push_back(texture_transform); + state->json["extensionsRequired"] = extensions_required; + return OK; +} + +Error GLTFDocument::_serialize_scenes(Ref<GLTFState> state) { + Array scenes; + const int loaded_scene = 0; + state->json["scene"] = loaded_scene; + + if (state->nodes.size()) { + Dictionary s; + if (!state->scene_name.is_empty()) { + s["name"] = state->scene_name; + } + + Array nodes; + nodes.push_back(0); + s["nodes"] = nodes; + scenes.push_back(s); + } + state->json["scenes"] = scenes; + + return OK; +} + +Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> state) { + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (!f) { + return err; + } + + Vector<uint8_t> array; + array.resize(f->get_len()); + f->get_buffer(array.ptrw(), array.size()); + String text; + text.parse_utf8((const char *)array.ptr(), array.size()); + + String err_txt; + int err_line; + Variant v; + err = JSON::parse(text, v, err_txt, err_line); + if (err != OK) { + _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); + return err; + } + state->json = v; + + return OK; +} + +Error GLTFDocument::_serialize_bone_attachment(Ref<GLTFState> state) { + for (int skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) { + for (int attachment_i = 0; attachment_i < state->skeletons[skeleton_i]->bone_attachments.size(); attachment_i++) { + BoneAttachment3D *bone_attachment = state->skeletons[skeleton_i]->bone_attachments[attachment_i]; + String bone_name = bone_attachment->get_bone_name(); + bone_name = _sanitize_bone_name(bone_name); + int32_t bone = state->skeletons[skeleton_i]->godot_skeleton->find_bone(bone_name); + ERR_CONTINUE(bone == -1); + for (int skin_i = 0; skin_i < state->skins.size(); skin_i++) { + if (state->skins[skin_i]->skeleton != skeleton_i) { + continue; + } + + for (int node_i = 0; node_i < bone_attachment->get_child_count(); node_i++) { + ERR_CONTINUE(bone >= state->skins[skin_i]->joints.size()); + _convert_scene_node(state, bone_attachment->get_child(node_i), bone_attachment->get_owner(), state->skins[skin_i]->joints[bone], 0); + } + break; + } + } + } + return OK; +} + +Error GLTFDocument::_parse_glb(const String &p_path, Ref<GLTFState> state) { + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (!f) { + return err; + } + + uint32_t magic = f->get_32(); + ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF + f->get_32(); // version + f->get_32(); // length + + uint32_t chunk_length = f->get_32(); + uint32_t chunk_type = f->get_32(); + + ERR_FAIL_COND_V(chunk_type != 0x4E4F534A, ERR_PARSE_ERROR); //JSON + Vector<uint8_t> json_data; + json_data.resize(chunk_length); + uint32_t len = f->get_buffer(json_data.ptrw(), chunk_length); + ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); + + String text; + text.parse_utf8((const char *)json_data.ptr(), json_data.size()); + + String err_txt; + int err_line; + Variant v; + err = JSON::parse(text, v, err_txt, err_line); + if (err != OK) { + _err_print_error("", p_path.utf8().get_data(), err_line, err_txt.utf8().get_data(), ERR_HANDLER_SCRIPT); + return err; + } + + state->json = v; + + //data? + + chunk_length = f->get_32(); + chunk_type = f->get_32(); + + if (f->eof_reached()) { + return OK; //all good + } + + ERR_FAIL_COND_V(chunk_type != 0x004E4942, ERR_PARSE_ERROR); //BIN + + state->glb_data.resize(chunk_length); + len = f->get_buffer(state->glb_data.ptrw(), chunk_length); + ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); + + return OK; +} + +static Array _vec3_to_arr(const Vector3 &p_vec3) { + Array array; + array.resize(3); + array[0] = p_vec3.x; + array[1] = p_vec3.y; + array[2] = p_vec3.z; + return array; +} + +static Vector3 _arr_to_vec3(const Array &p_array) { + ERR_FAIL_COND_V(p_array.size() != 3, Vector3()); + return Vector3(p_array[0], p_array[1], p_array[2]); +} + +static Array _quat_to_array(const Quat &p_quat) { + Array array; + array.resize(4); + array[0] = p_quat.x; + array[1] = p_quat.y; + array[2] = p_quat.z; + array[3] = p_quat.w; + return array; +} + +static Quat _arr_to_quat(const Array &p_array) { + ERR_FAIL_COND_V(p_array.size() != 4, Quat()); + return Quat(p_array[0], p_array[1], p_array[2], p_array[3]); +} + +static Transform _arr_to_xform(const Array &p_array) { + ERR_FAIL_COND_V(p_array.size() != 16, Transform()); + + Transform xform; + xform.basis.set_axis(Vector3::AXIS_X, Vector3(p_array[0], p_array[1], p_array[2])); + xform.basis.set_axis(Vector3::AXIS_Y, Vector3(p_array[4], p_array[5], p_array[6])); + xform.basis.set_axis(Vector3::AXIS_Z, Vector3(p_array[8], p_array[9], p_array[10])); + xform.set_origin(Vector3(p_array[12], p_array[13], p_array[14])); + + return xform; +} + +static Vector<real_t> _xform_to_array(const Transform p_transform) { + Vector<real_t> array; + array.resize(16); + Vector3 axis_x = p_transform.get_basis().get_axis(Vector3::AXIS_X); + array.write[0] = axis_x.x; + array.write[1] = axis_x.y; + array.write[2] = axis_x.z; + array.write[3] = 0.0f; + Vector3 axis_y = p_transform.get_basis().get_axis(Vector3::AXIS_Y); + array.write[4] = axis_y.x; + array.write[5] = axis_y.y; + array.write[6] = axis_y.z; + array.write[7] = 0.0f; + Vector3 axis_z = p_transform.get_basis().get_axis(Vector3::AXIS_Z); + array.write[8] = axis_z.x; + array.write[9] = axis_z.y; + array.write[10] = axis_z.z; + array.write[11] = 0.0f; + Vector3 origin = p_transform.get_origin(); + array.write[12] = origin.x; + array.write[13] = origin.y; + array.write[14] = origin.z; + array.write[15] = 1.0f; + return array; +} + +Error GLTFDocument::_serialize_nodes(Ref<GLTFState> state) { + Array nodes; + for (int i = 0; i < state->nodes.size(); i++) { + Dictionary node; + Ref<GLTFNode> n = state->nodes[i]; + Dictionary extensions; + node["extensions"] = extensions; + if (!n->get_name().is_empty()) { + node["name"] = n->get_name(); + } + if (n->camera != -1) { + node["camera"] = n->camera; + } + if (n->light != -1) { + Dictionary lights_punctual; + extensions["KHR_lights_punctual"] = lights_punctual; + lights_punctual["light"] = n->light; + } + if (n->mesh != -1) { + node["mesh"] = n->mesh; + } + if (n->skin != -1) { + node["skin"] = n->skin; + } + if (n->skeleton != -1 && n->skin < 0) { + } + if (n->xform != Transform()) { + node["matrix"] = _xform_to_array(n->xform); + } + + if (!n->rotation.is_equal_approx(Quat())) { + node["rotation"] = _quat_to_array(n->rotation); + } + + if (!n->scale.is_equal_approx(Vector3(1.0f, 1.0f, 1.0f))) { + node["scale"] = _vec3_to_arr(n->scale); + } + + if (!n->translation.is_equal_approx(Vector3())) { + node["translation"] = _vec3_to_arr(n->translation); + } + if (n->children.size()) { + Array children; + for (int j = 0; j < n->children.size(); j++) { + children.push_back(n->children[j]); + } + node["children"] = children; + } + nodes.push_back(node); + } + state->json["nodes"] = nodes; + return OK; +} + +String GLTFDocument::_sanitize_scene_name(const String &name) { + RegEx regex("([^a-zA-Z0-9_ -]+)"); + String p_name = regex.sub(name, "", true); + return p_name; +} + +String GLTFDocument::_gen_unique_name(Ref<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_nocolon(":"); + p_name = pattern_nocolon.sub(p_name, "_", true); + + RegEx pattern_noslash("/"); + p_name = pattern_noslash.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(Ref<GLTFState> state, const GLTFSkeletonIndex skel_i, const String &p_name) { + String s_name = _sanitize_bone_name(p_name); + if (s_name.is_empty()) { + s_name = "bone"; + } + String name; + int index = 1; + while (true) { + name = s_name; + + if (index > 1) { + name += "_" + itos(index); + } + if (!state->skeletons[skel_i]->unique_names.has(name)) { + break; + } + index++; + } + + state->skeletons.write[skel_i]->unique_names.insert(name); + + return name; +} + +Error GLTFDocument::_parse_scenes(Ref<GLTFState> state) { + ERR_FAIL_COND_V(!state->json.has("scenes"), ERR_FILE_CORRUPT); + const Array &scenes = state->json["scenes"]; + int loaded_scene = 0; + if (state->json.has("scene")) { + loaded_scene = state->json["scene"]; + } else { + WARN_PRINT("The load-time scene is not defined in the glTF2 file. Picking the first scene."); + } + + if (scenes.size()) { + ERR_FAIL_COND_V(loaded_scene >= scenes.size(), ERR_FILE_CORRUPT); + const Dictionary &s = scenes[loaded_scene]; + ERR_FAIL_COND_V(!s.has("nodes"), ERR_UNAVAILABLE); + const Array &nodes = s["nodes"]; + for (int j = 0; j < nodes.size(); j++) { + state->root_nodes.push_back(nodes[j]); + } + + if (s.has("name") && 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(Ref<GLTFState> state) { + ERR_FAIL_COND_V(!state->json.has("nodes"), ERR_FILE_CORRUPT); + const Array &nodes = state->json["nodes"]; + for (int i = 0; i < nodes.size(); i++) { + Ref<GLTFNode> node; + node.instance(); + const Dictionary &n = nodes[i]; + + if (n.has("name")) { + node->set_name(n["name"]); + } + if (n.has("camera")) { + node->camera = n["camera"]; + } + if (n.has("mesh")) { + node->mesh = n["mesh"]; + } + if (n.has("skin")) { + node->skin = n["skin"]; + } + if (n.has("matrix")) { + node->xform = _arr_to_xform(n["matrix"]); + } else { + if (n.has("translation")) { + node->translation = _arr_to_vec3(n["translation"]); + } + if (n.has("rotation")) { + node->rotation = _arr_to_quat(n["rotation"]); + } + if (n.has("scale")) { + node->scale = _arr_to_vec3(n["scale"]); + } + + node->xform.basis.set_quat_scale(node->rotation, node->scale); + node->xform.origin = node->translation; + } + + if (n.has("extensions")) { + Dictionary extensions = n["extensions"]; + if (extensions.has("KHR_lights_punctual")) { + Dictionary lights_punctual = extensions["KHR_lights_punctual"]; + if (lights_punctual.has("light")) { + GLTFLightIndex light = lights_punctual["light"]; + node->light = light; + } + } + } + + if (n.has("children")) { + const Array &children = n["children"]; + for (int j = 0; j < children.size(); j++) { + node->children.push_back(children[j]); + } + } + + state->nodes.push_back(node); + } + + // build the hierarchy + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; + + ERR_FAIL_INDEX_V(child_i, state->nodes.size(), ERR_FILE_CORRUPT); + ERR_CONTINUE(state->nodes[child_i]->parent != -1); //node already has a parent, wtf. + + state->nodes.write[child_i]->parent = node_i; + } + } + + _compute_node_heights(state); + + return OK; +} + +void GLTFDocument::_compute_node_heights(Ref<GLTFState> state) { + state->root_nodes.clear(); + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); ++node_i) { + Ref<GLTFNode> node = state->nodes[node_i]; + node->height = 0; + + GLTFNodeIndex current_i = node_i; + while (current_i >= 0) { + const GLTFNodeIndex parent_i = state->nodes[current_i]->parent; + if (parent_i >= 0) { + ++node->height; + } + current_i = parent_i; + } + + if (node->height == 0) { + state->root_nodes.push_back(node_i); + } + } +} + +static Vector<uint8_t> _parse_base64_uri(const String &uri) { + int start = uri.find(","); + ERR_FAIL_COND_V(start == -1, Vector<uint8_t>()); + + CharString substr = uri.right(start + 1).ascii(); + + int strlen = substr.length(); + + Vector<uint8_t> buf; + buf.resize(strlen / 4 * 3 + 1 + 1); + + size_t len = 0; + ERR_FAIL_COND_V(CryptoCore::b64_decode(buf.ptrw(), buf.size(), &len, (unsigned char *)substr.get_data(), strlen) != OK, Vector<uint8_t>()); + + buf.resize(len); + + return buf; +} +Error GLTFDocument::_encode_buffer_glb(Ref<GLTFState> state, const String &p_path) { + print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); + + if (!state->buffers.size()) { + return OK; + } + Array buffers; + if (state->buffers.size()) { + Vector<uint8_t> buffer_data = state->buffers[0]; + Dictionary gltf_buffer; + + gltf_buffer["byteLength"] = buffer_data.size(); + buffers.push_back(gltf_buffer); + } + + for (GLTFBufferIndex i = 1; i < state->buffers.size() - 1; i++) { + Vector<uint8_t> buffer_data = state->buffers[i]; + Dictionary gltf_buffer; + String filename = p_path.get_basename().get_file() + itos(i) + ".bin"; + String path = p_path.get_base_dir() + "/" + filename; + Error err; + FileAccessRef f = FileAccess::open(path, FileAccess::WRITE, &err); + if (!f) { + return err; + } + if (buffer_data.size() == 0) { + return OK; + } + f->create(FileAccess::ACCESS_RESOURCES); + f->store_buffer(buffer_data.ptr(), buffer_data.size()); + f->close(); + gltf_buffer["uri"] = filename; + gltf_buffer["byteLength"] = buffer_data.size(); + buffers.push_back(gltf_buffer); + } + state->json["buffers"] = buffers; + + return OK; +} + +Error GLTFDocument::_encode_buffer_bins(Ref<GLTFState> state, const String &p_path) { + print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); + + if (!state->buffers.size()) { + return OK; + } + Array buffers; + + for (GLTFBufferIndex i = 0; i < state->buffers.size(); i++) { + Vector<uint8_t> buffer_data = state->buffers[i]; + Dictionary gltf_buffer; + String filename = p_path.get_basename().get_file() + itos(i) + ".bin"; + String path = p_path.get_base_dir() + "/" + filename; + Error err; + FileAccessRef f = FileAccess::open(path, FileAccess::WRITE, &err); + if (!f) { + return err; + } + if (buffer_data.size() == 0) { + return OK; + } + f->create(FileAccess::ACCESS_RESOURCES); + f->store_buffer(buffer_data.ptr(), buffer_data.size()); + f->close(); + gltf_buffer["uri"] = filename; + gltf_buffer["byteLength"] = buffer_data.size(); + buffers.push_back(gltf_buffer); + } + state->json["buffers"] = buffers; + + return OK; +} + +Error GLTFDocument::_parse_buffers(Ref<GLTFState> state, const String &p_base_path) { + if (!state->json.has("buffers")) { + return OK; + } + + const Array &buffers = state->json["buffers"]; + for (GLTFBufferIndex i = 0; i < buffers.size(); i++) { + if (i == 0 && state->glb_data.size()) { + state->buffers.push_back(state->glb_data); + + } else { + const Dictionary &buffer = buffers[i]; + if (buffer.has("uri")) { + Vector<uint8_t> buffer_data; + String uri = buffer["uri"]; + + if (uri.begins_with("data:")) { // Embedded data using base64. + // Validate data MIME types and throw an error if it's one we don't know/support. + if (!uri.begins_with("data:application/octet-stream;base64") && + !uri.begins_with("data:application/gltf-buffer;base64")) { + ERR_PRINT("glTF: Got buffer with an unknown URI data type: " + uri); + } + buffer_data = _parse_base64_uri(uri); + } else { // Relative path to an external image file. + uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. + buffer_data = FileAccess::get_file_as_array(uri); + ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); + } + + ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); + int byteLength = buffer["byteLength"]; + ERR_FAIL_COND_V(byteLength < buffer_data.size(), ERR_PARSE_ERROR); + state->buffers.push_back(buffer_data); + } + } + } + + print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); + + return OK; +} + +Error GLTFDocument::_encode_buffer_views(Ref<GLTFState> state) { + Array buffers; + for (GLTFBufferViewIndex i = 0; i < state->buffer_views.size(); i++) { + Dictionary d; + + Ref<GLTFBufferView> buffer_view = state->buffer_views[i]; + + d["buffer"] = buffer_view->buffer; + d["byteLength"] = buffer_view->byte_length; + + d["byteOffset"] = buffer_view->byte_offset; + + if (buffer_view->byte_stride != -1) { + d["byteStride"] = buffer_view->byte_stride; + } + + // TODO Sparse + // d["target"] = buffer_view->indices; + + ERR_FAIL_COND_V(!d.has("buffer"), ERR_INVALID_DATA); + ERR_FAIL_COND_V(!d.has("byteLength"), ERR_INVALID_DATA); + buffers.push_back(d); + } + print_verbose("glTF: Total buffer views: " + itos(state->buffer_views.size())); + state->json["bufferViews"] = buffers; + return OK; +} + +Error GLTFDocument::_parse_buffer_views(Ref<GLTFState> state) { + 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]; + + Ref<GLTFBufferView> buffer_view; + buffer_view.instance(); + + ERR_FAIL_COND_V(!d.has("buffer"), ERR_PARSE_ERROR); + buffer_view->buffer = d["buffer"]; + ERR_FAIL_COND_V(!d.has("byteLength"), ERR_PARSE_ERROR); + buffer_view->byte_length = d["byteLength"]; + + if (d.has("byteOffset")) { + buffer_view->byte_offset = d["byteOffset"]; + } + + if (d.has("byteStride")) { + buffer_view->byte_stride = d["byteStride"]; + } + + if (d.has("target")) { + const int target = d["target"]; + buffer_view->indices = target == GLTFDocument::ELEMENT_ARRAY_BUFFER; + } + + state->buffer_views.push_back(buffer_view); + } + + print_verbose("glTF: Total buffer views: " + itos(state->buffer_views.size())); + + return OK; +} + +Error GLTFDocument::_encode_accessors(Ref<GLTFState> state) { + Array accessors; + for (GLTFAccessorIndex i = 0; i < state->accessors.size(); i++) { + Dictionary d; + + Ref<GLTFAccessor> accessor = state->accessors[i]; + d["componentType"] = accessor->component_type; + d["count"] = accessor->count; + d["type"] = _get_accessor_type_name(accessor->type); + d["byteOffset"] = accessor->byte_offset; + d["normalized"] = accessor->normalized; + 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(Ref<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]; + + Ref<GLTFAccessor> accessor; + accessor.instance(); + + ERR_FAIL_COND_V(!d.has("componentType"), ERR_PARSE_ERROR); + accessor->component_type = d["componentType"]; + ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR); + accessor->count = d["count"]; + ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); + accessor->type = _get_type_from_str(d["type"]); + + if (d.has("bufferView")) { + accessor->buffer_view = d["bufferView"]; //optional because it may be sparse... + } + + if (d.has("byteOffset")) { + accessor->byte_offset = d["byteOffset"]; + } + + if (d.has("normalized")) { + accessor->normalized = d["normalized"]; + } + + if (d.has("max")) { + 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; +} + +double GLTFDocument::_filter_number(double p_float) { + if (Math::is_nan(p_float)) { + return 0.0f; + } + return p_float; +} + +String GLTFDocument::_get_component_type_name(const uint32_t p_component) { + switch (p_component) { + case GLTFDocument::COMPONENT_TYPE_BYTE: + return "Byte"; + case GLTFDocument::COMPONENT_TYPE_UNSIGNED_BYTE: + return "UByte"; + case GLTFDocument::COMPONENT_TYPE_SHORT: + return "Short"; + case GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT: + return "UShort"; + case GLTFDocument::COMPONENT_TYPE_INT: + return "Int"; + case GLTFDocument::COMPONENT_TYPE_FLOAT: + return "Float"; + } + + return "<Error>"; +} + +String GLTFDocument::_get_type_name(const GLTFType p_component) { + static const char *names[] = { + "float", + "vec2", + "vec3", + "vec4", + "mat2", + "mat3", + "mat4" + }; + + return names[p_component]; +} + +Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, const int count, const GLTFType type, const int component_type, const bool normalized, const int byte_offset, const bool for_vertex, GLTFBufferViewIndex &r_accessor) { + const int component_count_for_type[7] = { + 1, 2, 3, 4, 4, 9, 16 + }; + + const int component_count = component_count_for_type[type]; + const int component_size = _get_component_type_size(component_type); + ERR_FAIL_COND_V(component_size == 0, FAILED); + + int skip_every = 0; + int skip_bytes = 0; + //special case of alignments, as described in spec + switch (component_type) { + case COMPONENT_TYPE_BYTE: + case COMPONENT_TYPE_UNSIGNED_BYTE: { + if (type == TYPE_MAT2) { + skip_every = 2; + skip_bytes = 2; + } + if (type == TYPE_MAT3) { + skip_every = 3; + skip_bytes = 1; + } + } break; + case COMPONENT_TYPE_SHORT: + case COMPONENT_TYPE_UNSIGNED_SHORT: { + if (type == TYPE_MAT3) { + skip_every = 6; + skip_bytes = 4; + } + } break; + default: { + } + } + + Ref<GLTFBufferView> bv; + bv.instance(); + const uint32_t offset = bv->byte_offset = byte_offset; + Vector<uint8_t> &gltf_buffer = state->buffers.write[0]; + + int stride = _get_component_type_size(component_type); + if (for_vertex && stride % 4) { + stride += 4 - (stride % 4); //according to spec must be multiple of 4 + } + //use to debug + print_verbose("glTF: encoding type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); + + print_verbose("glTF: encoding accessor offset " + itos(byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(gltf_buffer.size()) + " view len " + itos(bv->byte_length)); + + const int buffer_end = (stride * (count - 1)) + _get_component_type_size(component_type); + // TODO define bv->byte_stride + bv->byte_offset = gltf_buffer.size(); + + switch (component_type) { + case COMPONENT_TYPE_BYTE: { + Vector<int8_t> buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 128.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int8_t))); + 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<uint8_t> buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 255.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + gltf_buffer.append_array(buffer); + bv->byte_length = buffer.size() * sizeof(uint8_t); + } break; + case COMPONENT_TYPE_SHORT: { + Vector<int16_t> buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 32768.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int16_t))); + 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<uint16_t> buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + if (normalized) { + buffer.write[dst_i] = d * 65535.0; + } else { + buffer.write[dst_i] = d; + } + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint16_t))); + 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<int> buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + buffer.write[dst_i] = d; + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int32_t))); + 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<float> buffer; + buffer.resize(count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *src; + buffer.write[dst_i] = d; + src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(float))); + 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_accessor = bv->buffer = state->buffer_views.size(); + state->buffer_views.push_back(bv); + return OK; +} + +Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> state, double *dst, const GLTFBufferViewIndex p_buffer_view, const int skip_every, const int skip_bytes, const int element_size, const int count, const GLTFType type, const int component_count, const int component_type, const int component_size, const bool normalized, const int byte_offset, const bool for_vertex) { + const Ref<GLTFBufferView> bv = state->buffer_views[p_buffer_view]; + + int stride = element_size; + if (bv->byte_stride != -1) { + stride = bv->byte_stride; + } + if (for_vertex && stride % 4) { + stride += 4 - (stride % 4); //according to spec must be multiple of 4 + } + + ERR_FAIL_INDEX_V(bv->buffer, state->buffers.size(), ERR_PARSE_ERROR); + + const uint32_t offset = bv->byte_offset + byte_offset; + Vector<uint8_t> buffer = state->buffers[bv->buffer]; //copy on write, so no performance hit + const uint8_t *bufptr = buffer.ptr(); + + //use to debug + print_verbose("glTF: type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); + print_verbose("glTF: accessor offset " + itos(byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv->byte_length)); + + const int buffer_end = (stride * (count - 1)) + element_size; + ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_PARSE_ERROR); + + ERR_FAIL_COND_V((int)(offset + buffer_end) > buffer.size(), ERR_PARSE_ERROR); + + //fill everything as doubles + + for (int i = 0; i < count; i++) { + const uint8_t *src = &bufptr[offset + i * stride]; + + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + src += skip_bytes; + } + + double d = 0; + + switch (component_type) { + case COMPONENT_TYPE_BYTE: { + int8_t b = int8_t(*src); + if (normalized) { + d = (double(b) / 128.0); + } else { + d = double(b); + } + } break; + case COMPONENT_TYPE_UNSIGNED_BYTE: { + uint8_t b = *src; + if (normalized) { + d = (double(b) / 255.0); + } else { + d = double(b); + } + } break; + case COMPONENT_TYPE_SHORT: { + int16_t s = *(int16_t *)src; + if (normalized) { + d = (double(s) / 32768.0); + } else { + d = double(s); + } + } break; + case COMPONENT_TYPE_UNSIGNED_SHORT: { + uint16_t s = *(uint16_t *)src; + if (normalized) { + d = (double(s) / 65535.0); + } else { + d = double(s); + } + } break; + case COMPONENT_TYPE_INT: { + d = *(int *)src; + } break; + case COMPONENT_TYPE_FLOAT: { + d = *(float *)src; + } break; + } + + *dst++ = d; + src += component_size; + } + } + + return OK; +} + +int GLTFDocument::_get_component_type_size(const int component_type) { + switch (component_type) { + case COMPONENT_TYPE_BYTE: + case COMPONENT_TYPE_UNSIGNED_BYTE: + return 1; + break; + case COMPONENT_TYPE_SHORT: + case COMPONENT_TYPE_UNSIGNED_SHORT: + return 2; + break; + case COMPONENT_TYPE_INT: + case COMPONENT_TYPE_FLOAT: + return 4; + break; + default: { + ERR_FAIL_V(0); + } + } + return 0; +} + +Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + //spec, for reference: + //https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + + ERR_FAIL_INDEX_V(p_accessor, state->accessors.size(), Vector<double>()); + + const Ref<GLTFAccessor> a = state->accessors[p_accessor]; + + const int component_count_for_type[7] = { + 1, 2, 3, 4, 4, 9, 16 + }; + + const int component_count = component_count_for_type[a->type]; + const int component_size = _get_component_type_size(a->component_type); + ERR_FAIL_COND_V(component_size == 0, Vector<double>()); + int element_size = component_count * component_size; + + int skip_every = 0; + int skip_bytes = 0; + //special case of alignments, as described in spec + switch (a->component_type) { + case COMPONENT_TYPE_BYTE: + case COMPONENT_TYPE_UNSIGNED_BYTE: { + if (a->type == TYPE_MAT2) { + skip_every = 2; + skip_bytes = 2; + element_size = 8; //override for this case + } + if (a->type == TYPE_MAT3) { + skip_every = 3; + skip_bytes = 1; + element_size = 12; //override for this case + } + } break; + case COMPONENT_TYPE_SHORT: + case COMPONENT_TYPE_UNSIGNED_SHORT: { + if (a->type == TYPE_MAT3) { + skip_every = 6; + skip_bytes = 4; + element_size = 16; //override for this case + } + } break; + default: { + } + } + + Vector<double> dst_buffer; + dst_buffer.resize(component_count * a->count); + double *dst = dst_buffer.ptrw(); + + if (a->buffer_view >= 0) { + ERR_FAIL_INDEX_V(a->buffer_view, state->buffer_views.size(), Vector<double>()); + + const Error err = _decode_buffer_view(state, dst, a->buffer_view, skip_every, skip_bytes, element_size, a->count, a->type, component_count, a->component_type, component_size, a->normalized, a->byte_offset, p_for_vertex); + if (err != OK) + return Vector<double>(); + } else { + //fill with zeros, as bufferview is not defined. + for (int i = 0; i < (a->count * component_count); i++) { + dst_buffer.write[i] = 0; + } + } + + if (a->sparse_count > 0) { + // I could not find any file using this, so this code is so far untested + Vector<double> indices; + indices.resize(a->sparse_count); + const int indices_component_size = _get_component_type_size(a->sparse_indices_component_type); + + Error err = _decode_buffer_view(state, indices.ptrw(), a->sparse_indices_buffer_view, 0, 0, indices_component_size, a->sparse_count, TYPE_SCALAR, 1, a->sparse_indices_component_type, indices_component_size, false, a->sparse_indices_byte_offset, false); + if (err != OK) + return Vector<double>(); + + Vector<double> data; + data.resize(component_count * a->sparse_count); + err = _decode_buffer_view(state, data.ptrw(), a->sparse_values_buffer_view, skip_every, skip_bytes, element_size, a->sparse_count, a->type, component_count, a->component_type, component_size, a->normalized, a->sparse_values_byte_offset, p_for_vertex); + if (err != OK) + return Vector<double>(); + + for (int i = 0; i < indices.size(); i++) { + const int write_offset = int(indices[i]) * component_count; + + for (int j = 0; j < component_count; j++) { + dst[write_offset + j] = data[i * component_count + j]; + } + } + } + + return dst_buffer; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> state, const Vector<int32_t> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 1; + const int ret_size = p_attribs.size(); + Vector<double> attribs; + attribs.resize(ret_size); + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + attribs.write[i] = Math::stepify(p_attribs[i], 1.0); + if (i == 0) { + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = attribs[(i * element_count) + type_i]; + type_min.write[type_i] = attribs[(i * element_count) + type_i]; + } + } + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = MAX(attribs[(i * element_count) + type_i], type_max[type_i]); + type_min.write[type_i] = MIN(attribs[(i * element_count) + type_i], type_min[type_i]); + type_max.write[type_i] = _filter_number(type_max.write[type_i]); + type_min.write[type_i] = _filter_number(type_min.write[type_i]); + } + } + + ERR_FAIL_COND_V(attribs.size() == 0, -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_SCALAR; + const int component_type = GLTFDocument::COMPONENT_TYPE_INT; + + 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.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +Vector<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<int> ret; + + if (attribs.size() == 0) + return ret; + + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size(); + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = int(attribs_ptr[i]); + } + } + return ret; +} + +Vector<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<float> ret; + + if (attribs.size() == 0) + return ret; + + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size(); + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = float(attribs_ptr[i]); + } + } + return ret; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> state, const Vector<Vector2> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 2; + + const int ret_size = p_attribs.size() * element_count; + Vector<double> attribs; + attribs.resize(ret_size); + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + + for (int i = 0; i < p_attribs.size(); i++) { + Vector2 attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.y, CMP_NORMALIZE_TOLERANCE); + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC2; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + 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.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> state, const Vector<Color> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 4; + Vector<double> attribs; + attribs.resize(ret_size); + + const int element_count = 4; + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.r, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.g, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(attrib.b, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 3] = Math::stepify(attrib.a, CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC4; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + 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.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +void GLTFDocument::_calc_accessor_min_max(int i, const int element_count, Vector<double> &type_max, Vector<double> attribs, Vector<double> &type_min) { + if (i == 0) { + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = attribs[(i * element_count) + type_i]; + type_min.write[type_i] = attribs[(i * element_count) + type_i]; + } + } + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = MAX(attribs[(i * element_count) + type_i], type_max[type_i]); + type_min.write[type_i] = MIN(attribs[(i * element_count) + type_i], type_min[type_i]); + type_max.write[type_i] = _filter_number(type_max.write[type_i]); + type_min.write[type_i] = _filter_number(type_min.write[type_i]); + } +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> state, const Vector<Color> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + + const int ret_size = p_attribs.size() * 4; + Vector<double> attribs; + attribs.resize(ret_size); + + const int element_count = 4; + + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.r, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.g, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(attrib.b, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 3] = Math::stepify(attrib.a, CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC4; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + 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.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> state, const Vector<Color> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + + const int element_count = 4; + const int ret_size = p_attribs.size() * element_count; + Vector<double> attribs; + attribs.resize(ret_size); + + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Color attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.r, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.g, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(attrib.b, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 3] = Math::stepify(attrib.a, CMP_NORMALIZE_TOLERANCE); + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC4; + const int component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT; + + 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.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quats(Ref<GLTFState> state, const Vector<Quat> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 4; + + const int ret_size = p_attribs.size() * element_count; + Vector<double> attribs; + attribs.resize(ret_size); + + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Quat quat = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(quat.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(quat.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(quat.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 3] = Math::stepify(quat.w, CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC4; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + 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.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<Vector2> ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 2 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 2; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Vector2(attribs_ptr[i * 2 + 0], attribs_ptr[i * 2 + 1]); + } + } + return ret; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> state, const Vector<real_t> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 1; + const int ret_size = p_attribs.size(); + Vector<double> attribs; + attribs.resize(ret_size); + + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + + for (int i = 0; i < p_attribs.size(); i++) { + attribs.write[i] = Math::stepify(p_attribs[i], CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + + ERR_FAIL_COND_V(!attribs.size(), -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_SCALAR; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + 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.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> state, const Vector<Vector3> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 3; + const int ret_size = p_attribs.size() * element_count; + Vector<double> attribs; + attribs.resize(ret_size); + + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Vector3 attrib = p_attribs[i]; + attribs.write[(i * element_count) + 0] = Math::stepify(attrib.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 1] = Math::stepify(attrib.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[(i * element_count) + 2] = Math::stepify(attrib.z, CMP_NORMALIZE_TOLERANCE); + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_VEC3; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + 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.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> state, const Vector<Transform> p_attribs, const bool p_for_vertex) { + if (p_attribs.size() == 0) { + return -1; + } + const int element_count = 16; + const int ret_size = p_attribs.size() * element_count; + Vector<double> attribs; + attribs.resize(ret_size); + + Vector<double> type_max; + type_max.resize(element_count); + Vector<double> type_min; + type_min.resize(element_count); + for (int i = 0; i < p_attribs.size(); i++) { + Transform attrib = p_attribs[i]; + Basis basis = attrib.get_basis(); + Vector3 axis_0 = basis.get_axis(Vector3::AXIS_X); + + attribs.write[i * element_count + 0] = Math::stepify(axis_0.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 1] = Math::stepify(axis_0.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 2] = Math::stepify(axis_0.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 3] = 0.0; + + Vector3 axis_1 = basis.get_axis(Vector3::AXIS_Y); + attribs.write[i * element_count + 4] = Math::stepify(axis_1.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 5] = Math::stepify(axis_1.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 6] = Math::stepify(axis_1.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 7] = 0.0; + + Vector3 axis_2 = basis.get_axis(Vector3::AXIS_Z); + attribs.write[i * element_count + 8] = Math::stepify(axis_2.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 9] = Math::stepify(axis_2.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 10] = Math::stepify(axis_2.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 11] = 0.0; + + Vector3 origin = attrib.get_origin(); + attribs.write[i * element_count + 12] = Math::stepify(origin.x, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 13] = Math::stepify(origin.y, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 14] = Math::stepify(origin.z, CMP_NORMALIZE_TOLERANCE); + attribs.write[i * element_count + 15] = 1.0; + + _calc_accessor_min_max(i, element_count, type_max, attribs, type_min); + } + ERR_FAIL_COND_V(attribs.size() % element_count != 0, -1); + + Ref<GLTFAccessor> accessor; + accessor.instance(); + GLTFBufferIndex buffer_view_i; + int64_t size = state->buffers[0].size(); + const GLTFDocument::GLTFType type = GLTFDocument::TYPE_MAT4; + const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + + 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.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + if (err != OK) { + return -1; + } + accessor->buffer_view = buffer_view_i; + state->accessors.push_back(accessor); + return state->accessors.size() - 1; +} + +Vector<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<Vector3> ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 3 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 3; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Vector3(attribs_ptr[i * 3 + 0], attribs_ptr[i * 3 + 1], attribs_ptr[i * 3 + 2]); + } + } + return ret; +} + +Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<Color> ret; + + if (attribs.size() == 0) + return ret; + + const int type = state->accessors[p_accessor]->type; + ERR_FAIL_COND_V(!(type == TYPE_VEC3 || type == TYPE_VEC4), ret); + int vec_len = 3; + if (type == TYPE_VEC4) { + vec_len = 4; + } + + ERR_FAIL_COND_V(attribs.size() % vec_len != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / vec_len; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Color(attribs_ptr[i * vec_len + 0], attribs_ptr[i * vec_len + 1], attribs_ptr[i * vec_len + 2], vec_len == 4 ? attribs_ptr[i * 4 + 3] : 1.0); + } + } + return ret; +} +Vector<Quat> GLTFDocument::_decode_accessor_as_quat(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<Quat> ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); + const double *attribs_ptr = attribs.ptr(); + const int ret_size = attribs.size() / 4; + ret.resize(ret_size); + { + for (int i = 0; i < ret_size; i++) { + ret.write[i] = Quat(attribs_ptr[i * 4 + 0], attribs_ptr[i * 4 + 1], attribs_ptr[i * 4 + 2], attribs_ptr[i * 4 + 3]).normalized(); + } + } + return ret; +} +Vector<Transform2D> GLTFDocument::_decode_accessor_as_xform2d(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<Transform2D> ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 4 != 0, ret); + ret.resize(attribs.size() / 4); + for (int i = 0; i < ret.size(); i++) { + ret.write[i][0] = Vector2(attribs[i * 4 + 0], attribs[i * 4 + 1]); + ret.write[i][1] = Vector2(attribs[i * 4 + 2], attribs[i * 4 + 3]); + } + return ret; +} + +Vector<Basis> GLTFDocument::_decode_accessor_as_basis(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<Basis> ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 9 != 0, ret); + ret.resize(attribs.size() / 9); + for (int i = 0; i < ret.size(); i++) { + ret.write[i].set_axis(0, Vector3(attribs[i * 9 + 0], attribs[i * 9 + 1], attribs[i * 9 + 2])); + ret.write[i].set_axis(1, Vector3(attribs[i * 9 + 3], attribs[i * 9 + 4], attribs[i * 9 + 5])); + ret.write[i].set_axis(2, Vector3(attribs[i * 9 + 6], attribs[i * 9 + 7], attribs[i * 9 + 8])); + } + return ret; +} + +Vector<Transform> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); + Vector<Transform> ret; + + if (attribs.size() == 0) + return ret; + + ERR_FAIL_COND_V(attribs.size() % 16 != 0, ret); + ret.resize(attribs.size() / 16); + for (int i = 0; i < ret.size(); i++) { + ret.write[i].basis.set_axis(0, Vector3(attribs[i * 16 + 0], attribs[i * 16 + 1], attribs[i * 16 + 2])); + ret.write[i].basis.set_axis(1, Vector3(attribs[i * 16 + 4], attribs[i * 16 + 5], attribs[i * 16 + 6])); + ret.write[i].basis.set_axis(2, Vector3(attribs[i * 16 + 8], attribs[i * 16 + 9], attribs[i * 16 + 10])); + ret.write[i].set_origin(Vector3(attribs[i * 16 + 12], attribs[i * 16 + 13], attribs[i * 16 + 14])); + } + return ret; +} + +Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { + Array meshes; + for (GLTFMeshIndex gltf_mesh_i = 0; gltf_mesh_i < state->meshes.size(); gltf_mesh_i++) { + print_verbose("glTF: Serializing mesh: " + itos(gltf_mesh_i)); + Ref<EditorSceneImporterMesh> import_mesh = state->meshes.write[gltf_mesh_i]->get_mesh(); + if (import_mesh.is_null()) { + continue; + } + Array primitives; + Array targets; + Dictionary gltf_mesh; + Array target_names; + Array weights; + for (int surface_i = 0; surface_i < import_mesh->get_surface_count(); surface_i++) { + Dictionary primitive; + Mesh::PrimitiveType primitive_type = import_mesh->get_surface_primitive_type(surface_i); + switch (primitive_type) { + case Mesh::PRIMITIVE_POINTS: { + primitive["mode"] = 0; + break; + } + case Mesh::PRIMITIVE_LINES: { + primitive["mode"] = 1; + break; + } + // case Mesh::PRIMITIVE_LINE_LOOP: { + // primitive["mode"] = 2; + // break; + // } + case Mesh::PRIMITIVE_LINE_STRIP: { + primitive["mode"] = 3; + break; + } + case Mesh::PRIMITIVE_TRIANGLES: { + primitive["mode"] = 4; + break; + } + case Mesh::PRIMITIVE_TRIANGLE_STRIP: { + primitive["mode"] = 5; + break; + } + // case Mesh::PRIMITIVE_TRIANGLE_FAN: { + // primitive["mode"] = 6; + // break; + // } + default: { + ERR_FAIL_V(FAILED); + } + } + + Array array = import_mesh->get_surface_arrays(surface_i); + Dictionary attributes; + { + Vector<Vector3> a = array[Mesh::ARRAY_VERTEX]; + ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA); + attributes["POSITION"] = _encode_accessor_as_vec3(state, a, true); + } + { + Vector<real_t> a = array[Mesh::ARRAY_TANGENT]; + if (a.size()) { + const int ret_size = a.size() / 4; + Vector<Color> attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + Color out; + out.r = a[(i * 4) + 0]; + out.g = a[(i * 4) + 1]; + out.b = a[(i * 4) + 2]; + out.a = a[(i * 4) + 3]; + attribs.write[i] = out; + } + attributes["TANGENT"] = _encode_accessor_as_color(state, attribs, true); + } + } + { + Vector<Vector3> a = array[Mesh::ARRAY_NORMAL]; + if (a.size()) { + const int ret_size = a.size(); + Vector<Vector3> attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + attribs.write[i] = Vector3(a[i]).normalized(); + } + attributes["NORMAL"] = _encode_accessor_as_vec3(state, attribs, true); + } + } + { + Vector<Vector2> a = array[Mesh::ARRAY_TEX_UV]; + if (a.size()) { + attributes["TEXCOORD_0"] = _encode_accessor_as_vec2(state, a, true); + } + } + { + Vector<Vector2> a = array[Mesh::ARRAY_TEX_UV2]; + if (a.size()) { + attributes["TEXCOORD_1"] = _encode_accessor_as_vec2(state, a, true); + } + } + { + Vector<Color> a = array[Mesh::ARRAY_COLOR]; + if (a.size()) { + attributes["COLOR_0"] = _encode_accessor_as_color(state, a, true); + } + } + Map<int, int> joint_i_to_bone_i; + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + GLTFSkinIndex skin_i = -1; + if (state->nodes[node_i]->mesh == gltf_mesh_i) { + skin_i = state->nodes[node_i]->skin; + } + if (skin_i != -1) { + joint_i_to_bone_i = state->skins[skin_i]->joint_i_to_bone_i; + break; + } + } + { + Array a = array[Mesh::ARRAY_BONES]; + if (a.size()) { + const int ret_size = a.size() / 4; + Vector<Color> attribs; + attribs.resize(ret_size); + { + for (int array_i = 0; array_i < attribs.size(); array_i++) { + int32_t joint_0 = a[(array_i * 4) + 0]; + int32_t joint_1 = a[(array_i * 4) + 1]; + int32_t joint_2 = a[(array_i * 4) + 2]; + int32_t joint_3 = a[(array_i * 4) + 3]; + attribs.write[array_i] = Color(joint_0, joint_1, joint_2, joint_3); + } + } + attributes["JOINTS_0"] = _encode_accessor_as_joints(state, attribs, true); + } + } + { + Array a = array[Mesh::ARRAY_WEIGHTS]; + if (a.size()) { + const int ret_size = a.size() / 4; + Vector<Color> attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + attribs.write[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); + } + } + { + Vector<int32_t> mesh_indices = array[Mesh::ARRAY_INDEX]; + if (mesh_indices.size()) { + if (primitive_type == Mesh::PRIMITIVE_TRIANGLES) { + //swap around indices, convert ccw to cw for front face + const int is = mesh_indices.size(); + for (int k = 0; k < is; k += 3) { + SWAP(mesh_indices.write[k + 0], mesh_indices.write[k + 2]); + } + } + primitive["indices"] = _encode_accessor_as_ints(state, mesh_indices, true); + } else { + if (primitive_type == Mesh::PRIMITIVE_TRIANGLES) { + //generate indices because they need to be swapped for CW/CCW + const Vector<Vector3> &vertices = array[Mesh::ARRAY_VERTEX]; + Ref<SurfaceTool> st; + st.instance(); + st->create_from_triangle_arrays(array); + st->index(); + Vector<int32_t> generated_indices = st->commit_to_arrays()[Mesh::ARRAY_INDEX]; + const int vs = vertices.size(); + generated_indices.resize(vs); + { + for (int k = 0; k < vs; k += 3) { + generated_indices.write[k] = k; + generated_indices.write[k + 1] = k + 2; + generated_indices.write[k + 2] = k + 1; + } + } + primitive["indices"] = _encode_accessor_as_ints(state, generated_indices, true); + } + } + } + + primitive["attributes"] = attributes; + + //blend shapes + print_verbose("glTF: Mesh has targets"); + if (import_mesh->get_blend_shape_count()) { + ArrayMesh::BlendShapeMode shape_mode = import_mesh->get_blend_shape_mode(); + for (int morph_i = 0; morph_i < import_mesh->get_blend_shape_count(); morph_i++) { + Array array_morph = import_mesh->get_surface_blend_shape_arrays(surface_i, morph_i); + target_names.push_back(import_mesh->get_blend_shape_name(morph_i)); + Dictionary t; + Vector<Vector3> varr = array_morph[Mesh::ARRAY_VERTEX]; + Array mesh_arrays = import_mesh->get_surface_arrays(surface_i); + if (varr.size()) { + Vector<Vector3> src_varr = array[Mesh::ARRAY_VERTEX]; + if (shape_mode == ArrayMesh::BlendShapeMode::BLEND_SHAPE_MODE_NORMALIZED) { + const int max_idx = src_varr.size(); + for (int blend_i = 0; blend_i < max_idx; blend_i++) { + varr.write[blend_i] = Vector3(varr[blend_i]) - src_varr[blend_i]; + } + } + + t["POSITION"] = _encode_accessor_as_vec3(state, varr, true); + } + + Vector<Vector3> narr = array_morph[Mesh::ARRAY_NORMAL]; + if (varr.size()) { + t["NORMAL"] = _encode_accessor_as_vec3(state, narr, true); + } + Vector<real_t> tarr = array_morph[Mesh::ARRAY_TANGENT]; + if (tarr.size()) { + const int ret_size = tarr.size() / 4; + Vector<Color> attribs; + attribs.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + Color tangent; + tangent.r = tarr[(i * 4) + 0]; + tangent.r = tarr[(i * 4) + 1]; + tangent.r = tarr[(i * 4) + 2]; + tangent.r = tarr[(i * 4) + 3]; + } + t["TANGENT"] = _encode_accessor_as_color(state, attribs, true); + } + targets.push_back(t); + } + } + + Ref<BaseMaterial3D> mat = import_mesh->get_surface_material(surface_i); + if (mat.is_valid()) { + Map<Ref<BaseMaterial3D>, GLTFMaterialIndex>::Element *material_cache_i = state->material_cache.find(mat); + if (material_cache_i && material_cache_i->get() != -1) { + primitive["material"] = material_cache_i->get(); + } else { + GLTFMaterialIndex mat_i = state->materials.size(); + state->materials.push_back(mat); + primitive["material"] = mat_i; + state->material_cache.insert(mat, mat_i); + } + } + + if (targets.size()) { + primitive["targets"] = targets; + } + + primitives.push_back(primitive); + } + + Dictionary e; + e["targetNames"] = target_names; + + for (int j = 0; j < target_names.size(); j++) { + real_t weight = 0; + if (j < state->meshes.write[gltf_mesh_i]->get_blend_weights().size()) { + weight = state->meshes.write[gltf_mesh_i]->get_blend_weights()[j]; + } + weights.push_back(weight); + } + if (weights.size()) { + gltf_mesh["weights"] = weights; + } + + ERR_FAIL_COND_V(target_names.size() != weights.size(), FAILED); + + gltf_mesh["extras"] = e; + + gltf_mesh["primitives"] = primitives; + + meshes.push_back(gltf_mesh); + } + + state->json["meshes"] = meshes; + print_verbose("glTF: Total meshes: " + itos(meshes.size())); + + return OK; +} + +Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { + if (!state->json.has("meshes")) { + return OK; + } + + Array meshes = state->json["meshes"]; + for (GLTFMeshIndex i = 0; i < meshes.size(); i++) { + print_verbose("glTF: Parsing mesh: " + itos(i)); + Dictionary d = meshes[i]; + + Ref<GLTFMesh> mesh; + mesh.instance(); + bool has_vertex_color = false; + + ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); + + Array primitives = d["primitives"]; + const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + Ref<EditorSceneImporterMesh> import_mesh; + import_mesh.instance(); + for (int j = 0; j < primitives.size(); j++) { + Dictionary p = primitives[j]; + + Array array; + array.resize(Mesh::ARRAY_MAX); + + ERR_FAIL_COND_V(!p.has("attributes"), ERR_PARSE_ERROR); + + Dictionary a = p["attributes"]; + + Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; + if (p.has("mode")) { + const int mode = p["mode"]; + ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT); + static const Mesh::PrimitiveType primitives2[7] = { + Mesh::PRIMITIVE_POINTS, + Mesh::PRIMITIVE_LINES, + Mesh::PRIMITIVE_LINES, //loop not supported, should ce converted + Mesh::PRIMITIVE_LINES, + Mesh::PRIMITIVE_TRIANGLES, + Mesh::PRIMITIVE_TRIANGLE_STRIP, + Mesh::PRIMITIVE_TRIANGLES, //fan not supported, should be converted +#ifndef _MSC_VER +#warning line loop and triangle fan are not supported and need to be converted to lines and triangles +#endif + + }; + + primitive = primitives2[mode]; + } + + ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR); + if (a.has("POSITION")) { + array[Mesh::ARRAY_VERTEX] = _decode_accessor_as_vec3(state, a["POSITION"], true); + } + if (a.has("NORMAL")) { + array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); + } + if (a.has("TANGENT")) { + array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(state, a["TANGENT"], true); + } + if (a.has("TEXCOORD_0")) { + array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(state, a["TEXCOORD_0"], true); + } + if (a.has("TEXCOORD_1")) { + array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(state, a["TEXCOORD_1"], true); + } + if (a.has("COLOR_0")) { + array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(state, a["COLOR_0"], true); + has_vertex_color = true; + } + if (a.has("JOINTS_0")) { + array[Mesh::ARRAY_BONES] = _decode_accessor_as_ints(state, a["JOINTS_0"], true); + } + if (a.has("WEIGHTS_0")) { + Vector<float> weights = _decode_accessor_as_floats(state, a["WEIGHTS_0"], true); + { //gltf does not seem to normalize the weights for some reason.. + int wc = weights.size(); + float *w = weights.ptrw(); + + for (int k = 0; k < wc; k += 4) { + float total = 0.0; + total += w[k + 0]; + total += w[k + 1]; + total += w[k + 2]; + total += w[k + 3]; + if (total > 0.0) { + w[k + 0] /= total; + w[k + 1] /= total; + w[k + 2] /= total; + w[k + 3] /= total; + } + } + } + array[Mesh::ARRAY_WEIGHTS] = weights; + } + + if (p.has("indices")) { + Vector<int> indices = _decode_accessor_as_ints(state, p["indices"], false); + + if (primitive == Mesh::PRIMITIVE_TRIANGLES) { + //swap around indices, convert ccw to cw for front face + + const int is = indices.size(); + int *w = indices.ptrw(); + for (int k = 0; k < is; k += 3) { + SWAP(w[k + 1], w[k + 2]); + } + } + array[Mesh::ARRAY_INDEX] = indices; + + } else if (primitive == Mesh::PRIMITIVE_TRIANGLES) { + //generate indices because they need to be swapped for CW/CCW + const Vector<Vector3> &vertices = array[Mesh::ARRAY_VERTEX]; + ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR); + Vector<int> indices; + const int vs = vertices.size(); + indices.resize(vs); + { + int *w = indices.ptrw(); + for (int k = 0; k < vs; k += 3) { + w[k] = k; + w[k + 1] = k + 2; + w[k + 2] = k + 1; + } + } + array[Mesh::ARRAY_INDEX] = indices; + } + + bool generate_tangents = (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("TEXCOORD_0") && a.has("NORMAL")); + + if (generate_tangents) { + //must generate mikktspace tangents.. ergh.. + Ref<SurfaceTool> st; + st.instance(); + st->create_from_triangle_arrays(array); + st->generate_tangents(); + array = st->commit_to_arrays(); + } + + Array morphs; + //blend shapes + if (p.has("targets")) { + print_verbose("glTF: Mesh has targets"); + const Array &targets = p["targets"]; + + //ideally BLEND_SHAPE_MODE_RELATIVE since gltf2 stores in displacement + //but it could require a larger refactor? + import_mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); + + if (j == 0) { + const Array &target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array(); + for (int k = 0; k < targets.size(); k++) { + const String name = k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k); + import_mesh->add_blend_shape(name); + } + } + + for (int k = 0; k < targets.size(); k++) { + const Dictionary &t = targets[k]; + + Array array_copy; + array_copy.resize(Mesh::ARRAY_MAX); + + for (int l = 0; l < Mesh::ARRAY_MAX; l++) { + array_copy[l] = array[l]; + } + + array_copy[Mesh::ARRAY_INDEX] = Variant(); + + if (t.has("POSITION")) { + Vector<Vector3> varr = _decode_accessor_as_vec3(state, t["POSITION"], true); + const Vector<Vector3> src_varr = array[Mesh::ARRAY_VERTEX]; + const int size = src_varr.size(); + ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); + { + const int max_idx = varr.size(); + varr.resize(size); + + Vector3 *w_varr = varr.ptrw(); + const Vector3 *r_varr = varr.ptr(); + const Vector3 *r_src_varr = src_varr.ptr(); + for (int l = 0; l < size; l++) { + if (l < max_idx) { + w_varr[l] = r_varr[l] + r_src_varr[l]; + } else { + w_varr[l] = r_src_varr[l]; + } + } + } + array_copy[Mesh::ARRAY_VERTEX] = varr; + } + if (t.has("NORMAL")) { + Vector<Vector3> narr = _decode_accessor_as_vec3(state, t["NORMAL"], true); + const Vector<Vector3> src_narr = array[Mesh::ARRAY_NORMAL]; + int size = src_narr.size(); + ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); + { + int max_idx = narr.size(); + narr.resize(size); + + Vector3 *w_narr = narr.ptrw(); + const Vector3 *r_narr = narr.ptr(); + const Vector3 *r_src_narr = src_narr.ptr(); + for (int l = 0; l < size; l++) { + if (l < max_idx) { + w_narr[l] = r_narr[l] + r_src_narr[l]; + } else { + w_narr[l] = r_src_narr[l]; + } + } + } + array_copy[Mesh::ARRAY_NORMAL] = narr; + } + if (t.has("TANGENT")) { + const Vector<Vector3> tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true); + const Vector<float> src_tangents = array[Mesh::ARRAY_TANGENT]; + ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR); + + Vector<float> tangents_v4; + + { + int max_idx = tangents_v3.size(); + + int size4 = src_tangents.size(); + tangents_v4.resize(size4); + float *w4 = tangents_v4.ptrw(); + + const Vector3 *r3 = tangents_v3.ptr(); + const float *r4 = src_tangents.ptr(); + + for (int l = 0; l < size4 / 4; l++) { + if (l < max_idx) { + w4[l * 4 + 0] = r3[l].x + r4[l * 4 + 0]; + w4[l * 4 + 1] = r3[l].y + r4[l * 4 + 1]; + w4[l * 4 + 2] = r3[l].z + r4[l * 4 + 2]; + } else { + w4[l * 4 + 0] = r4[l * 4 + 0]; + w4[l * 4 + 1] = r4[l * 4 + 1]; + w4[l * 4 + 2] = r4[l * 4 + 2]; + } + w4[l * 4 + 3] = r4[l * 4 + 3]; //copy flip value + } + } + + array_copy[Mesh::ARRAY_TANGENT] = tangents_v4; + } + + if (generate_tangents) { + Ref<SurfaceTool> st; + st.instance(); + st->create_from_triangle_arrays(array_copy); + st->deindex(); + st->generate_tangents(); + array_copy = st->commit_to_arrays(); + } + + morphs.push_back(array_copy); + } + } + + //just add it + + Ref<BaseMaterial3D> mat; + if (p.has("material")) { + const int material = p["material"]; + ERR_FAIL_INDEX_V(material, state->materials.size(), ERR_FILE_CORRUPT); + Ref<BaseMaterial3D> mat3d = state->materials[material]; + if (has_vertex_color) { + mat3d->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + } + mat = mat3d; + + } else if (has_vertex_color) { + Ref<StandardMaterial3D> mat3d; + mat3d.instance(); + mat3d->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + mat = mat3d; + } + + import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat); + } + + Vector<float> blend_weights; + blend_weights.resize(import_mesh->get_blend_shape_count()); + for (int32_t weight_i = 0; weight_i < blend_weights.size(); weight_i++) { + blend_weights.write[weight_i] = 0.0f; + } + + if (d.has("weights")) { + const Array &weights = d["weights"]; + for (int j = 0; j < weights.size(); j++) { + blend_weights.write[j] = weights[j]; + } + mesh->set_blend_weights(blend_weights); + } + mesh->set_mesh(import_mesh); + + state->meshes.push_back(mesh); + } + + print_verbose("glTF: Total meshes: " + itos(state->meshes.size())); + + return OK; +} + +Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path) { + Array images; + for (int i = 0; i < state->images.size(); i++) { + Dictionary d; + + ERR_CONTINUE(state->images[i].is_null()); + + Ref<Image> image = state->images[i]->get_data(); + ERR_CONTINUE(image.is_null()); + + if (p_path.to_lower().ends_with("glb")) { + GLTFBufferViewIndex bvi; + + Ref<GLTFBufferView> bv; + bv.instance(); + + const GLTFBufferIndex bi = 0; + bv->buffer = bi; + bv->byte_offset = state->buffers[bi].size(); + ERR_FAIL_INDEX_V(bi, state->buffers.size(), ERR_PARAMETER_RANGE_ERROR); + + Vector<uint8_t> buffer; + Ref<ImageTexture> img_tex = image; + if (img_tex.is_valid()) { + image = img_tex->get_data(); + } + Error err = PNGDriverCommon::image_to_png(image, buffer); + ERR_FAIL_COND_V_MSG(err, err, "Can't convert image to PNG."); + + bv->byte_length = buffer.size(); + state->buffers.write[bi].resize(state->buffers[bi].size() + bv->byte_length); + copymem(&state->buffers.write[bi].write[bv->byte_offset], buffer.ptr(), buffer.size()); + ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > state->buffers[bi].size(), ERR_FILE_CORRUPT); + + state->buffer_views.push_back(bv); + bvi = state->buffer_views.size() - 1; + d["bufferView"] = bvi; + d["mimeType"] = "image/png"; + } else { + String name = state->images[i]->get_name(); + if (name.is_empty()) { + name = itos(i); + } + name = _gen_unique_name(state, name); + name = name.pad_zeros(3); + Ref<_Directory> dir; + dir.instance(); + String texture_dir = "textures"; + String new_texture_dir = p_path.get_base_dir() + "/" + texture_dir; + dir->open(p_path.get_base_dir()); + if (!dir->dir_exists(new_texture_dir)) { + dir->make_dir(new_texture_dir); + } + name = name + ".png"; + image->save_png(new_texture_dir.plus_file(name)); + d["uri"] = texture_dir.plus_file(name); + } + images.push_back(d); + } + + print_verbose("Total images: " + itos(state->images.size())); + + if (!images.size()) { + return OK; + } + state->json["images"] = images; + + return OK; +} + +Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_path) { + if (!state->json.has("images")) { + return OK; + } + + // Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#images + + const Array &images = state->json["images"]; + for (int i = 0; i < images.size(); i++) { + const Dictionary &d = images[i]; + + // glTF 2.0 supports PNG and JPEG types, which can be specified as (from spec): + // "- a URI to an external file in one of the supported images formats, or + // - a URI with embedded base64-encoded data, or + // - a reference to a bufferView; in that case mimeType must be defined." + // Since mimeType is optional for external files and base64 data, we'll have to + // fall back on letting Godot parse the data to figure out if it's PNG or JPEG. + + // We'll assume that we use either URI or bufferView, so let's warn the user + // if their image somehow uses both. And fail if it has neither. + ERR_CONTINUE_MSG(!d.has("uri") && !d.has("bufferView"), "Invalid image definition in glTF file, it should specific an 'uri' or 'bufferView'."); + if (d.has("uri") && d.has("bufferView")) { + WARN_PRINT("Invalid image definition in glTF file using both 'uri' and 'bufferView'. 'bufferView' will take precedence."); + } + + String mimetype; + if (d.has("mimeType")) { // Should be "image/png" or "image/jpeg". + mimetype = d["mimeType"]; + } + + Vector<uint8_t> data; + const uint8_t *data_ptr = nullptr; + int data_size = 0; + + if (d.has("uri")) { + // Handles the first two bullet points from the spec (embedded data, or external file). + String uri = d["uri"]; + + if (uri.begins_with("data:")) { // Embedded data using base64. + // Validate data MIME types and throw a warning if it's one we don't know/support. + if (!uri.begins_with("data:application/octet-stream;base64") && + !uri.begins_with("data:application/gltf-buffer;base64") && + !uri.begins_with("data:image/png;base64") && + !uri.begins_with("data:image/jpeg;base64")) { + WARN_PRINT(vformat("glTF: Image index '%d' uses an unsupported URI data type: %s. Skipping it.", i, uri)); + state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count. + continue; + } + data = _parse_base64_uri(uri); + data_ptr = data.ptr(); + data_size = data.size(); + // mimeType is optional, but if we have it defined in the URI, let's use it. + if (mimetype.is_empty()) { + if (uri.begins_with("data:image/png;base64")) { + mimetype = "image/png"; + } else if (uri.begins_with("data:image/jpeg;base64")) { + mimetype = "image/jpeg"; + } + } + } else { // Relative path to an external image file. + uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. + // The spec says that if mimeType is defined, we should enforce it. + // So we should only rely on ResourceLoader::load if mimeType is not defined, + // otherwise we should use the same logic as for buffers. + if (mimetype == "image/png" || mimetype == "image/jpeg") { + // Load data buffer and rely on PNG and JPEG-specific logic below to load the image. + // This makes it possible to load a file with a wrong extension but correct MIME type, + // e.g. "foo.jpg" containing PNG data and with MIME type "image/png". ResourceLoader would fail. + data = FileAccess::get_file_as_array(uri); + ERR_FAIL_COND_V_MSG(data.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load image file as an array: " + uri); + data_ptr = data.ptr(); + data_size = data.size(); + } else { + // Good old ResourceLoader will rely on file extension. + Ref<Texture2D> texture = ResourceLoader::load(uri); + state->images.push_back(texture); + continue; + } + } + } else if (d.has("bufferView")) { + // Handles the third bullet point from the spec (bufferView). + ERR_FAIL_COND_V_MSG(mimetype.is_empty(), ERR_FILE_CORRUPT, + vformat("glTF: Image index '%d' specifies 'bufferView' but no 'mimeType', which is invalid.", i)); + + const GLTFBufferViewIndex bvi = d["bufferView"]; + + ERR_FAIL_INDEX_V(bvi, state->buffer_views.size(), ERR_PARAMETER_RANGE_ERROR); + + Ref<GLTFBufferView> bv = state->buffer_views[bvi]; + + const GLTFBufferIndex bi = bv->buffer; + ERR_FAIL_INDEX_V(bi, state->buffers.size(), ERR_PARAMETER_RANGE_ERROR); + + ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > state->buffers[bi].size(), ERR_FILE_CORRUPT); + + data_ptr = &state->buffers[bi][bv->byte_offset]; + data_size = bv->byte_length; + } + + Ref<Image> img; + + if (mimetype == "image/png") { // Load buffer as PNG. + ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE); + img = Image::_png_mem_loader_func(data_ptr, data_size); + } else if (mimetype == "image/jpeg") { // Loader buffer as JPEG. + ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE); + img = Image::_jpg_mem_loader_func(data_ptr, data_size); + } else { + // We can land here if we got an URI with base64-encoded data with application/* MIME type, + // and the optional mimeType property was not defined to tell us how to handle this data (or was invalid). + // So let's try PNG first, then JPEG. + ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE); + img = Image::_png_mem_loader_func(data_ptr, data_size); + if (img.is_null()) { + ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE); + img = Image::_jpg_mem_loader_func(data_ptr, data_size); + } + } + + ERR_FAIL_COND_V_MSG(img.is_null(), ERR_FILE_CORRUPT, + vformat("glTF: Couldn't load image index '%d' with its given mimetype: %s.", i, mimetype)); + + Ref<ImageTexture> t; + t.instance(); + t->create_from_image(img); + + state->images.push_back(t); + } + + print_verbose("glTF: Total images: " + itos(state->images.size())); + + return OK; +} + +Error GLTFDocument::_serialize_textures(Ref<GLTFState> state) { + if (!state->textures.size()) { + return OK; + } + + Array textures; + for (int32_t i = 0; i < state->textures.size(); i++) { + Dictionary d; + Ref<GLTFTexture> t = state->textures[i]; + ERR_CONTINUE(t->get_src_image() == -1); + d["source"] = t->get_src_image(); + textures.push_back(d); + } + state->json["textures"] = textures; + + return OK; +} + +Error GLTFDocument::_parse_textures(Ref<GLTFState> state) { + if (!state->json.has("textures")) + return OK; + + const Array &textures = state->json["textures"]; + for (GLTFTextureIndex i = 0; i < textures.size(); i++) { + const Dictionary &d = textures[i]; + + ERR_FAIL_COND_V(!d.has("source"), ERR_PARSE_ERROR); + + Ref<GLTFTexture> t; + t.instance(); + t->set_src_image(d["source"]); + state->textures.push_back(t); + } + + return OK; +} + +GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture) { + ERR_FAIL_COND_V(p_texture.is_null(), -1); + Ref<GLTFTexture> gltf_texture; + gltf_texture.instance(); + ERR_FAIL_COND_V(p_texture->get_data().is_null(), -1); + GLTFImageIndex gltf_src_image_i = state->images.size(); + state->images.push_back(p_texture); + gltf_texture->set_src_image(gltf_src_image_i); + GLTFTextureIndex gltf_texture_i = state->textures.size(); + state->textures.push_back(gltf_texture); + return gltf_texture_i; +} + +Ref<Texture2D> GLTFDocument::_get_texture(Ref<GLTFState> state, const GLTFTextureIndex p_texture) { + ERR_FAIL_INDEX_V(p_texture, state->textures.size(), Ref<Texture2D>()); + const GLTFImageIndex image = state->textures[p_texture]->get_src_image(); + + ERR_FAIL_INDEX_V(image, state->images.size(), Ref<Texture2D>()); + + return state->images[image]; +} + +Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { + Array materials; + for (int32_t i = 0; i < state->materials.size(); i++) { + Dictionary d; + + Ref<BaseMaterial3D> material = state->materials[i]; + if (material.is_null()) { + materials.push_back(d); + continue; + } + if (!material->get_name().is_empty()) { + d["name"] = _gen_unique_name(state, material->get_name()); + } + { + Dictionary mr; + { + Array arr; + const Color c = material->get_albedo().to_linear(); + arr.push_back(c.r); + arr.push_back(c.g); + arr.push_back(c.b); + arr.push_back(c.a); + mr["baseColorFactor"] = arr; + } + { + Dictionary bct; + Ref<Texture2D> albedo_texture = material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); + GLTFTextureIndex gltf_texture_index = -1; + + if (albedo_texture.is_valid() && albedo_texture->get_data().is_valid()) { + albedo_texture->set_name(material->get_name() + "_albedo"); + gltf_texture_index = _set_texture(state, albedo_texture); + } + if (gltf_texture_index != -1) { + bct["index"] = gltf_texture_index; + bct["extensions"] = _serialize_texture_transform_uv1(material); + mr["baseColorTexture"] = bct; + } + } + + mr["metallicFactor"] = material->get_metallic(); + mr["roughnessFactor"] = material->get_roughness(); + bool has_roughness = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_data().is_valid(); + bool has_ao = material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid(); + bool has_metalness = material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_data().is_valid(); + if (has_ao || has_roughness || has_metalness) { + Dictionary mrt; + Ref<Texture2D> roughness_texture = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS); + BaseMaterial3D::TextureChannel roughness_channel = material->get_roughness_texture_channel(); + Ref<Texture2D> metallic_texture = material->get_texture(BaseMaterial3D::TEXTURE_METALLIC); + BaseMaterial3D::TextureChannel metalness_channel = material->get_metallic_texture_channel(); + Ref<Texture2D> ao_texture = material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION); + BaseMaterial3D::TextureChannel ao_channel = material->get_ao_texture_channel(); + Ref<ImageTexture> orm_texture; + orm_texture.instance(); + Ref<Image> orm_image; + orm_image.instance(); + int32_t height = 0; + int32_t width = 0; + Ref<Image> ao_image; + if (has_ao) { + height = ao_texture->get_height(); + width = ao_texture->get_width(); + ao_image = ao_texture->get_data(); + Ref<ImageTexture> img_tex = ao_image; + if (img_tex.is_valid()) { + ao_image = img_tex->get_data(); + } + if (ao_image->is_compressed()) { + ao_image->decompress(); + } + } + Ref<Image> roughness_image; + if (has_roughness) { + height = roughness_texture->get_height(); + width = roughness_texture->get_width(); + roughness_image = roughness_texture->get_data(); + Ref<ImageTexture> img_tex = roughness_image; + if (img_tex.is_valid()) { + roughness_image = img_tex->get_data(); + } + if (roughness_image->is_compressed()) { + roughness_image->decompress(); + } + } + Ref<Image> metallness_image; + if (has_metalness) { + height = metallic_texture->get_height(); + width = metallic_texture->get_width(); + metallness_image = metallic_texture->get_data(); + Ref<ImageTexture> img_tex = metallness_image; + if (img_tex.is_valid()) { + metallness_image = img_tex->get_data(); + } + if (metallness_image->is_compressed()) { + metallness_image->decompress(); + } + } + Ref<Texture2D> albedo_texture = material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); + if (albedo_texture.is_valid() && albedo_texture->get_data().is_valid()) { + height = albedo_texture->get_height(); + width = albedo_texture->get_width(); + } + orm_image->create(width, height, false, Image::FORMAT_RGBA8); + if (ao_image.is_valid() && ao_image->get_size() != Vector2(width, height)) { + ao_image->resize(width, height, Image::INTERPOLATE_LANCZOS); + } + if (roughness_image.is_valid() && roughness_image->get_size() != Vector2(width, height)) { + roughness_image->resize(width, height, Image::INTERPOLATE_LANCZOS); + } + if (metallness_image.is_valid() && metallness_image->get_size() != Vector2(width, height)) { + metallness_image->resize(width, height, Image::INTERPOLATE_LANCZOS); + } + for (int32_t h = 0; h < height; h++) { + for (int32_t w = 0; w < width; w++) { + Color c = Color(1.0f, 1.0f, 1.0f); + if (has_ao) { + if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) { + c.r = ao_image->get_pixel(w, h).r; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) { + c.r = ao_image->get_pixel(w, h).g; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) { + c.r = ao_image->get_pixel(w, h).b; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) { + c.r = ao_image->get_pixel(w, h).a; + } + } + if (has_roughness) { + if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).r; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).g; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).b; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).a; + } + } + if (has_metalness) { + if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).r; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).g; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).b; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).a; + } + } + orm_image->set_pixel(w, h, c); + } + } + orm_image->generate_mipmaps(); + orm_texture->create_from_image(orm_image); + GLTFTextureIndex orm_texture_index = -1; + if (has_ao || has_roughness || has_metalness) { + orm_texture->set_name(material->get_name() + "_orm"); + orm_texture_index = _set_texture(state, orm_texture); + } + if (has_ao) { + Dictionary ot; + ot["index"] = orm_texture_index; + d["occlusionTexture"] = ot; + } + if (has_roughness || has_metalness) { + mrt["index"] = orm_texture_index; + mrt["extensions"] = _serialize_texture_transform_uv1(material); + mr["metallicRoughnessTexture"] = mrt; + } + } + d["pbrMetallicRoughness"] = mr; + } + + if (material->get_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING)) { + Dictionary nt; + Ref<ImageTexture> tex; + tex.instance(); + { + Ref<Texture2D> normal_texture = material->get_texture(BaseMaterial3D::TEXTURE_NORMAL); + // Code for uncompressing RG normal maps + Ref<Image> img = normal_texture->get_data(); + Ref<ImageTexture> img_tex = img; + if (img_tex.is_valid()) { + img = img_tex->get_data(); + } + img->decompress(); + img->convert(Image::FORMAT_RGBA8); + for (int32_t y = 0; y < img->get_height(); y++) { + for (int32_t x = 0; x < img->get_width(); x++) { + Color c = img->get_pixel(x, y); + Vector2 red_green = Vector2(c.r, c.g); + red_green = red_green * Vector2(2.0f, 2.0f) - Vector2(1.0f, 1.0f); + float blue = 1.0f - red_green.dot(red_green); + blue = MAX(0.0f, blue); + c.b = Math::sqrt(blue); + img->set_pixel(x, y, c); + } + } + tex->create_from_image(img); + } + Ref<Texture2D> normal_texture = material->get_texture(BaseMaterial3D::TEXTURE_NORMAL); + GLTFTextureIndex gltf_texture_index = -1; + if (tex.is_valid() && tex->get_data().is_valid()) { + tex->set_name(material->get_name() + "_normal"); + gltf_texture_index = _set_texture(state, tex); + } + nt["scale"] = material->get_normal_scale(); + if (gltf_texture_index != -1) { + nt["index"] = gltf_texture_index; + d["normalTexture"] = nt; + } + } + + if (material->get_feature(BaseMaterial3D::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(BaseMaterial3D::FEATURE_EMISSION)) { + Dictionary et; + Ref<Texture2D> emission_texture = material->get_texture(BaseMaterial3D::TEXTURE_EMISSION); + GLTFTextureIndex gltf_texture_index = -1; + if (emission_texture.is_valid() && emission_texture->get_data().is_valid()) { + emission_texture->set_name(material->get_name() + "_emission"); + gltf_texture_index = _set_texture(state, emission_texture); + } + + if (gltf_texture_index != -1) { + et["index"] = gltf_texture_index; + d["emissiveTexture"] = et; + } + } + const bool ds = material->get_cull_mode() == BaseMaterial3D::CULL_DISABLED; + if (ds) { + d["doubleSided"] = ds; + } + if (material->get_transparency() == BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR) { + d["alphaMode"] = "MASK"; + d["alphaCutoff"] = material->get_alpha_scissor_threshold(); + } else if (material->get_transparency() != BaseMaterial3D::TRANSPARENCY_DISABLED) { + d["alphaMode"] = "BLEND"; + } + materials.push_back(d); + } + state->json["materials"] = materials; + print_verbose("Total materials: " + itos(state->materials.size())); + + return OK; +} + +Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { + if (!state->json.has("materials")) + return OK; + + const Array &materials = state->json["materials"]; + for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { + const Dictionary &d = materials[i]; + + Ref<StandardMaterial3D> material; + material.instance(); + if (d.has("name")) { + material->set_name(d["name"]); + } + material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + Dictionary pbr_spec_gloss_extensions; + if (d.has("extensions")) { + pbr_spec_gloss_extensions = d["extensions"]; + } + if (pbr_spec_gloss_extensions.has("KHR_materials_pbrSpecularGlossiness")) { + WARN_PRINT("Material uses a specular and glossiness workflow. Textures will be converted to roughness and metallic workflow, which may not be 100% accurate."); + Dictionary sgm = pbr_spec_gloss_extensions["KHR_materials_pbrSpecularGlossiness"]; + + Ref<GLTFSpecGloss> spec_gloss; + spec_gloss.instance(); + if (sgm.has("diffuseTexture")) { + const Dictionary &diffuse_texture_dict = sgm["diffuseTexture"]; + if (diffuse_texture_dict.has("index")) { + Ref<Texture2D> diffuse_texture = _get_texture(state, diffuse_texture_dict["index"]); + if (diffuse_texture.is_valid()) { + spec_gloss->diffuse_img = diffuse_texture->get_data(); + material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, diffuse_texture); + } + } + } + if (sgm.has("diffuseFactor")) { + const Array &arr = sgm["diffuseFactor"]; + ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); + spec_gloss->diffuse_factor = c; + material->set_albedo(spec_gloss->diffuse_factor); + } + + if (sgm.has("specularFactor")) { + const Array &arr = sgm["specularFactor"]; + ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); + spec_gloss->specular_factor = Color(arr[0], arr[1], arr[2]); + } + + if (sgm.has("glossinessFactor")) { + spec_gloss->gloss_factor = sgm["glossinessFactor"]; + material->set_roughness(1.0f - CLAMP(spec_gloss->gloss_factor, 0.0f, 1.0f)); + } + if (sgm.has("specularGlossinessTexture")) { + const Dictionary &spec_gloss_texture = sgm["specularGlossinessTexture"]; + if (spec_gloss_texture.has("index")) { + const Ref<Texture2D> orig_texture = _get_texture(state, spec_gloss_texture["index"]); + if (orig_texture.is_valid()) { + spec_gloss->spec_gloss_img = orig_texture->get_data(); + } + } + } + spec_gloss_to_rough_metal(spec_gloss, material); + + } else if (d.has("pbrMetallicRoughness")) { + const Dictionary &mr = d["pbrMetallicRoughness"]; + if (mr.has("baseColorFactor")) { + const Array &arr = mr["baseColorFactor"]; + ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2], arr[3]).to_srgb(); + material->set_albedo(c); + } + + if (mr.has("baseColorTexture")) { + const Dictionary &bct = mr["baseColorTexture"]; + if (bct.has("index")) { + material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, _get_texture(state, bct["index"])); + } + if (!mr.has("baseColorFactor")) { + material->set_albedo(Color(1, 1, 1)); + } + _set_texture_transform_uv1(bct, material); + } + + if (mr.has("metallicFactor")) { + material->set_metallic(mr["metallicFactor"]); + } else { + material->set_metallic(1.0); + } + + if (mr.has("roughnessFactor")) { + material->set_roughness(mr["roughnessFactor"]); + } else { + material->set_roughness(1.0); + } + + if (mr.has("metallicRoughnessTexture")) { + const Dictionary &bct = mr["metallicRoughnessTexture"]; + if (bct.has("index")) { + const Ref<Texture2D> t = _get_texture(state, bct["index"]); + material->set_texture(BaseMaterial3D::TEXTURE_METALLIC, t); + material->set_metallic_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_BLUE); + material->set_texture(BaseMaterial3D::TEXTURE_ROUGHNESS, t); + material->set_roughness_texture_channel(BaseMaterial3D::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(BaseMaterial3D::TEXTURE_NORMAL, _get_texture(state, bct["index"])); + material->set_feature(BaseMaterial3D::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(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION, _get_texture(state, bct["index"])); + material->set_ao_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_RED); + material->set_feature(BaseMaterial3D::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(BaseMaterial3D::FEATURE_EMISSION, true); + + material->set_emission(c); + } + + if (d.has("emissiveTexture")) { + const Dictionary &bct = d["emissiveTexture"]; + if (bct.has("index")) { + material->set_texture(BaseMaterial3D::TEXTURE_EMISSION, _get_texture(state, bct["index"])); + material->set_feature(BaseMaterial3D::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(BaseMaterial3D::CULL_DISABLED); + } + } + + if (d.has("alphaMode")) { + const String &am = d["alphaMode"]; + if (am == "BLEND") { + material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); + } else if (am == "MASK") { + material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR); + if (d.has("alphaCutoff")) { + material->set_alpha_scissor_threshold(d["alphaCutoff"]); + } else { + material->set_alpha_scissor_threshold(0.5f); + } + } + } + state->materials.push_back(material); + } + + print_verbose("Total materials: " + itos(state->materials.size())); + + return OK; +} + +void GLTFDocument::_set_texture_transform_uv1(const Dictionary &d, Ref<BaseMaterial3D> material) { + if (d.has("extensions")) { + const Dictionary &extensions = d["extensions"]; + if (extensions.has("KHR_texture_transform")) { + const Dictionary &texture_transform = extensions["KHR_texture_transform"]; + const Array &offset_arr = texture_transform["offset"]; + if (offset_arr.size() == 2) { + const Vector3 offset_vector3 = Vector3(offset_arr[0], offset_arr[1], 0.0f); + material->set_uv1_offset(offset_vector3); + } + + const Array &scale_arr = texture_transform["scale"]; + if (scale_arr.size() == 2) { + const Vector3 scale_vector3 = Vector3(scale_arr[0], scale_arr[1], 1.0f); + material->set_uv1_scale(scale_vector3); + } + } + } +} + +void GLTFDocument::spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss, Ref<BaseMaterial3D> p_material) { + if (r_spec_gloss->spec_gloss_img.is_null()) { + return; + } + if (r_spec_gloss->diffuse_img.is_null()) { + return; + } + Ref<Image> rm_img; + rm_img.instance(); + bool has_roughness = false; + bool has_metal = false; + p_material->set_roughness(1.0f); + p_material->set_metallic(1.0f); + rm_img->create(r_spec_gloss->spec_gloss_img->get_width(), r_spec_gloss->spec_gloss_img->get_height(), false, Image::FORMAT_RGBA8); + r_spec_gloss->spec_gloss_img->decompress(); + if (r_spec_gloss->diffuse_img.is_valid()) { + r_spec_gloss->diffuse_img->decompress(); + r_spec_gloss->diffuse_img->resize(r_spec_gloss->spec_gloss_img->get_width(), r_spec_gloss->spec_gloss_img->get_height(), Image::INTERPOLATE_LANCZOS); + r_spec_gloss->spec_gloss_img->resize(r_spec_gloss->diffuse_img->get_width(), r_spec_gloss->diffuse_img->get_height(), Image::INTERPOLATE_LANCZOS); + } + for (int32_t y = 0; y < r_spec_gloss->spec_gloss_img->get_height(); y++) { + for (int32_t x = 0; x < r_spec_gloss->spec_gloss_img->get_width(); x++) { + const Color specular_pixel = r_spec_gloss->spec_gloss_img->get_pixel(x, y).to_linear(); + Color specular = Color(specular_pixel.r, specular_pixel.g, specular_pixel.b); + specular *= r_spec_gloss->specular_factor; + Color diffuse = Color(1.0f, 1.0f, 1.0f); + diffuse *= r_spec_gloss->diffuse_img->get_pixel(x, y).to_linear(); + float metallic = 0.0f; + Color base_color; + spec_gloss_to_metal_base_color(specular, diffuse, base_color, metallic); + Color mr = Color(1.0f, 1.0f, 1.0f); + mr.g = specular_pixel.a; + mr.b = metallic; + if (!Math::is_equal_approx(mr.g, 1.0f)) { + has_roughness = true; + } + if (!Math::is_equal_approx(mr.b, 0.0f)) { + has_metal = true; + } + mr.g *= r_spec_gloss->gloss_factor; + mr.g = 1.0f - mr.g; + rm_img->set_pixel(x, y, mr); + if (r_spec_gloss->diffuse_img.is_valid()) { + r_spec_gloss->diffuse_img->set_pixel(x, y, base_color.to_srgb()); + } + } + } + rm_img->generate_mipmaps(); + r_spec_gloss->diffuse_img->generate_mipmaps(); + Ref<ImageTexture> diffuse_image_texture; + diffuse_image_texture.instance(); + diffuse_image_texture->create_from_image(r_spec_gloss->diffuse_img); + p_material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, diffuse_image_texture); + Ref<ImageTexture> rm_image_texture; + rm_image_texture.instance(); + rm_image_texture->create_from_image(rm_img); + if (has_roughness) { + p_material->set_texture(BaseMaterial3D::TEXTURE_ROUGHNESS, rm_image_texture); + p_material->set_roughness_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_GREEN); + } + + if (has_metal) { + p_material->set_texture(BaseMaterial3D::TEXTURE_METALLIC, rm_image_texture); + p_material->set_metallic_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_BLUE); + } +} + +void GLTFDocument::spec_gloss_to_metal_base_color(const Color &p_specular_factor, const Color &p_diffuse, Color &r_base_color, float &r_metallic) { + const Color DIELECTRIC_SPECULAR = Color(0.04f, 0.04f, 0.04f); + Color specular = Color(p_specular_factor.r, p_specular_factor.g, p_specular_factor.b); + const float one_minus_specular_strength = 1.0f - get_max_component(specular); + const float dielectric_specular_red = DIELECTRIC_SPECULAR.r; + float brightness_diffuse = get_perceived_brightness(p_diffuse); + const float brightness_specular = get_perceived_brightness(specular); + r_metallic = solve_metallic(dielectric_specular_red, brightness_diffuse, brightness_specular, one_minus_specular_strength); + const float one_minus_metallic = 1.0f - r_metallic; + const Color base_color_from_diffuse = p_diffuse * (one_minus_specular_strength / (1.0f - dielectric_specular_red) / MAX(one_minus_metallic, CMP_EPSILON)); + const Color base_color_from_specular = (specular - (DIELECTRIC_SPECULAR * (one_minus_metallic))) * (1.0f / MAX(r_metallic, CMP_EPSILON)); + r_base_color.r = Math::lerp(base_color_from_diffuse.r, base_color_from_specular.r, r_metallic * r_metallic); + r_base_color.g = Math::lerp(base_color_from_diffuse.g, base_color_from_specular.g, r_metallic * r_metallic); + r_base_color.b = Math::lerp(base_color_from_diffuse.b, base_color_from_specular.b, r_metallic * r_metallic); + r_base_color.a = p_diffuse.a; + r_base_color.r = CLAMP(r_base_color.r, 0.0f, 1.0f); + r_base_color.g = CLAMP(r_base_color.g, 0.0f, 1.0f); + r_base_color.b = CLAMP(r_base_color.b, 0.0f, 1.0f); + r_base_color.a = CLAMP(r_base_color.a, 0.0f, 1.0f); +} + +GLTFNodeIndex GLTFDocument::_find_highest_node(Ref<GLTFState> state, const Vector<GLTFNodeIndex> &subset) { + int highest = -1; + GLTFNodeIndex best_node = -1; + + for (int i = 0; i < subset.size(); ++i) { + const GLTFNodeIndex node_i = subset[i]; + const Ref<GLTFNode> node = state->nodes[node_i]; + + if (highest == -1 || node->height < highest) { + highest = node->height; + best_node = node_i; + } + } + + return best_node; +} + +bool GLTFDocument::_capture_nodes_in_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin, const GLTFNodeIndex node_index) { + bool found_joint = false; + + for (int i = 0; i < state->nodes[node_index]->children.size(); ++i) { + found_joint |= _capture_nodes_in_skin(state, skin, state->nodes[node_index]->children[i]); + } + + if (found_joint) { + // Mark it if we happen to find another skins joint... + if (state->nodes[node_index]->joint && skin->joints.find(node_index) < 0) { + skin->joints.push_back(node_index); + } else if (skin->non_joints.find(node_index) < 0) { + skin->non_joints.push_back(node_index); + } + } + + if (skin->joints.find(node_index) > 0) { + return true; + } + + return false; +} + +void GLTFDocument::_capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { + DisjointSet<GLTFNodeIndex> disjoint_set; + + for (int i = 0; i < skin->joints.size(); ++i) { + const GLTFNodeIndex node_index = skin->joints[i]; + const GLTFNodeIndex parent = state->nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (skin->joints.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector<GLTFNodeIndex> roots; + disjoint_set.get_representatives(roots); + + if (roots.size() <= 1) { + return; + } + + int maxHeight = -1; + + // Determine the max height rooted tree + for (int i = 0; i < roots.size(); ++i) { + const GLTFNodeIndex root = roots[i]; + + if (maxHeight == -1 || state->nodes[root]->height < maxHeight) { + maxHeight = state->nodes[root]->height; + } + } + + // Go up the tree till all of the multiple roots of the skin are at the same hierarchy level. + // This sucks, but 99% of all game engines (not just Godot) would have this same issue. + for (int i = 0; i < roots.size(); ++i) { + GLTFNodeIndex current_node = roots[i]; + while (state->nodes[current_node]->height > maxHeight) { + GLTFNodeIndex parent = state->nodes[current_node]->parent; + + if (state->nodes[parent]->joint && skin->joints.find(parent) < 0) { + skin->joints.push_back(parent); + } else if (skin->non_joints.find(parent) < 0) { + skin->non_joints.push_back(parent); + } + + current_node = parent; + } + + // replace the roots + roots.write[i] = current_node; + } + + // Climb up the tree until they all have the same parent + bool all_same; + + do { + all_same = true; + const GLTFNodeIndex first_parent = state->nodes[roots[0]]->parent; + + for (int i = 1; i < roots.size(); ++i) { + all_same &= (first_parent == state->nodes[roots[i]]->parent); + } + + if (!all_same) { + for (int i = 0; i < roots.size(); ++i) { + const GLTFNodeIndex current_node = roots[i]; + const GLTFNodeIndex parent = state->nodes[current_node]->parent; + + if (state->nodes[parent]->joint && skin->joints.find(parent) < 0) { + skin->joints.push_back(parent); + } else if (skin->non_joints.find(parent) < 0) { + skin->non_joints.push_back(parent); + } + + roots.write[i] = parent; + } + } + + } while (!all_same); +} + +Error GLTFDocument::_expand_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { + _capture_nodes_for_multirooted_skin(state, skin); + + // Grab all nodes that lay in between skin joints/nodes + DisjointSet<GLTFNodeIndex> disjoint_set; + + Vector<GLTFNodeIndex> all_skin_nodes; + all_skin_nodes.append_array(skin->joints); + all_skin_nodes.append_array(skin->non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state->nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector<GLTFNodeIndex> out_owners; + disjoint_set.get_representatives(out_owners); + + Vector<GLTFNodeIndex> out_roots; + + for (int i = 0; i < out_owners.size(); ++i) { + Vector<GLTFNodeIndex> set; + disjoint_set.get_members(set, out_owners[i]); + + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + out_roots.push_back(root); + } + + out_roots.sort(); + + for (int i = 0; i < out_roots.size(); ++i) { + _capture_nodes_in_skin(state, skin, out_roots[i]); + } + + skin->roots = out_roots; + + return OK; +} + +Error GLTFDocument::_verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { + // This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is) + // In case additional interpolating logic is added to the skins, this will help ensure that you + // do not cause it to self implode into a fiery blaze + + // We are going to re-calculate the root nodes and compare them to the ones saved in the skin, + // then ensure the multiple trees (if they exist) are on the same sublevel + + // Grab all nodes that lay in between skin joints/nodes + DisjointSet<GLTFNodeIndex> disjoint_set; + + Vector<GLTFNodeIndex> all_skin_nodes; + all_skin_nodes.append_array(skin->joints); + all_skin_nodes.append_array(skin->non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state->nodes[node_index]->parent; + disjoint_set.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + disjoint_set.create_union(parent, node_index); + } + } + + Vector<GLTFNodeIndex> out_owners; + disjoint_set.get_representatives(out_owners); + + Vector<GLTFNodeIndex> out_roots; + + for (int i = 0; i < out_owners.size(); ++i) { + Vector<GLTFNodeIndex> set; + disjoint_set.get_members(set, out_owners[i]); + + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + out_roots.push_back(root); + } + + out_roots.sort(); + + ERR_FAIL_COND_V(out_roots.size() == 0, FAILED); + + // Make sure the roots are the exact same (they better be) + ERR_FAIL_COND_V(out_roots.size() != skin->roots.size(), FAILED); + for (int i = 0; i < out_roots.size(); ++i) { + ERR_FAIL_COND_V(out_roots[i] != skin->roots[i], FAILED); + } + + // Single rooted skin? Perfectly ok! + if (out_roots.size() == 1) { + return OK; + } + + // Make sure all parents of a multi-rooted skin are the SAME + const GLTFNodeIndex parent = state->nodes[out_roots[0]]->parent; + for (int i = 1; i < out_roots.size(); ++i) { + if (state->nodes[out_roots[i]]->parent != parent) { + return FAILED; + } + } + + return OK; +} + +Error GLTFDocument::_parse_skins(Ref<GLTFState> state) { + if (!state->json.has("skins")) + return OK; + + const Array &skins = state->json["skins"]; + + // Create the base skins, and mark nodes that are joints + for (int i = 0; i < skins.size(); i++) { + const Dictionary &d = skins[i]; + + Ref<GLTFSkin> skin; + skin.instance(); + + ERR_FAIL_COND_V(!d.has("joints"), ERR_PARSE_ERROR); + + const Array &joints = d["joints"]; + + if (d.has("inverseBindMatrices")) { + skin->inverse_binds = _decode_accessor_as_xform(state, d["inverseBindMatrices"], false); + ERR_FAIL_COND_V(skin->inverse_binds.size() != joints.size(), ERR_PARSE_ERROR); + } + + for (int j = 0; j < joints.size(); j++) { + const GLTFNodeIndex node = joints[j]; + ERR_FAIL_INDEX_V(node, state->nodes.size(), ERR_PARSE_ERROR); + + skin->joints.push_back(node); + skin->joints_original.push_back(node); + + state->nodes.write[node]->joint = true; + } + + if (d.has("name")) { + skin->set_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) { + Ref<GLTFSkin> skin = state->skins.write[i]; + + // Expand the skin to capture all the extra non-joints that lie in between the actual joints, + // and expand the hierarchy to ensure multi-rooted trees lie on the same height level + ERR_FAIL_COND_V(_expand_skin(state, skin), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(_verify_skin(state, skin), ERR_PARSE_ERROR); + } + + print_verbose("glTF: Total skins: " + itos(state->skins.size())); + + return OK; +} + +Error GLTFDocument::_determine_skeletons(Ref<GLTFState> state) { + // Using a disjoint set, we are going to potentially combine all skins that are actually branches + // of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton. + // This is another unclear issue caused by the current glTF specification. + + DisjointSet<GLTFNodeIndex> skeleton_sets; + + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + const Ref<GLTFSkin> skin = state->skins[skin_i]; + + Vector<GLTFNodeIndex> all_skin_nodes; + all_skin_nodes.append_array(skin->joints); + all_skin_nodes.append_array(skin->non_joints); + + for (int i = 0; i < all_skin_nodes.size(); ++i) { + const GLTFNodeIndex node_index = all_skin_nodes[i]; + const GLTFNodeIndex parent = state->nodes[node_index]->parent; + skeleton_sets.insert(node_index); + + if (all_skin_nodes.find(parent) >= 0) { + skeleton_sets.create_union(parent, node_index); + } + } + + // We are going to connect the separate skin subtrees in each skin together + // so that the final roots are entire sets of valid skin trees + for (int i = 1; i < skin->roots.size(); ++i) { + skeleton_sets.create_union(skin->roots[0], skin->roots[i]); + } + } + + { // attempt to joint all touching subsets (siblings/parent are part of another skin) + Vector<GLTFNodeIndex> groups_representatives; + skeleton_sets.get_representatives(groups_representatives); + + Vector<GLTFNodeIndex> highest_group_members; + Vector<Vector<GLTFNodeIndex>> groups; + for (int i = 0; i < groups_representatives.size(); ++i) { + Vector<GLTFNodeIndex> group; + skeleton_sets.get_members(group, groups_representatives[i]); + highest_group_members.push_back(_find_highest_node(state, group)); + groups.push_back(group); + } + + for (int i = 0; i < highest_group_members.size(); ++i) { + const GLTFNodeIndex node_i = highest_group_members[i]; + + // Attach any siblings together (this needs to be done n^2/2 times) + for (int j = i + 1; j < highest_group_members.size(); ++j) { + const GLTFNodeIndex node_j = highest_group_members[j]; + + // Even if they are siblings under the root! :) + if (state->nodes[node_i]->parent == state->nodes[node_j]->parent) { + skeleton_sets.create_union(node_i, node_j); + } + } + + // Attach any parenting going on together (we need to do this n^2 times) + const GLTFNodeIndex node_i_parent = state->nodes[node_i]->parent; + if (node_i_parent >= 0) { + for (int j = 0; j < groups.size() && i != j; ++j) { + const Vector<GLTFNodeIndex> &group = groups[j]; + + if (group.find(node_i_parent) >= 0) { + const GLTFNodeIndex node_j = highest_group_members[j]; + skeleton_sets.create_union(node_i, node_j); + } + } + } + } + } + + // At this point, the skeleton groups should be finalized + Vector<GLTFNodeIndex> skeleton_owners; + skeleton_sets.get_representatives(skeleton_owners); + + // Mark all the skins actual skeletons, after we have merged them + for (GLTFSkeletonIndex skel_i = 0; skel_i < skeleton_owners.size(); ++skel_i) { + const GLTFNodeIndex skeleton_owner = skeleton_owners[skel_i]; + Ref<GLTFSkeleton> skeleton; + skeleton.instance(); + + Vector<GLTFNodeIndex> skeleton_nodes; + skeleton_sets.get_members(skeleton_nodes, skeleton_owner); + + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref<GLTFSkin> skin = state->skins.write[skin_i]; + + // If any of the the skeletons nodes exist in a skin, that skin now maps to the skeleton + for (int i = 0; i < skeleton_nodes.size(); ++i) { + GLTFNodeIndex skel_node_i = skeleton_nodes[i]; + if (skin->joints.find(skel_node_i) >= 0 || skin->non_joints.find(skel_node_i) >= 0) { + skin->skeleton = skel_i; + continue; + } + } + } + + Vector<GLTFNodeIndex> non_joints; + for (int i = 0; i < skeleton_nodes.size(); ++i) { + const GLTFNodeIndex node_i = skeleton_nodes[i]; + + if (state->nodes[node_i]->joint) { + skeleton->joints.push_back(node_i); + } else { + non_joints.push_back(node_i); + } + } + + state->skeletons.push_back(skeleton); + + _reparent_non_joint_skeleton_subtrees(state, state->skeletons.write[skel_i], non_joints); + } + + for (GLTFSkeletonIndex skel_i = 0; skel_i < state->skeletons.size(); ++skel_i) { + Ref<GLTFSkeleton> skeleton = state->skeletons.write[skel_i]; + + for (int i = 0; i < skeleton->joints.size(); ++i) { + const GLTFNodeIndex node_i = skeleton->joints[i]; + Ref<GLTFNode> node = state->nodes[node_i]; + + ERR_FAIL_COND_V(!node->joint, ERR_PARSE_ERROR); + ERR_FAIL_COND_V(node->skeleton >= 0, ERR_PARSE_ERROR); + node->skeleton = skel_i; + } + + ERR_FAIL_COND_V(_determine_skeleton_roots(state, skel_i), ERR_PARSE_ERROR); + } + + return OK; +} + +Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton, const Vector<GLTFNodeIndex> &non_joints) { + DisjointSet<GLTFNodeIndex> subtree_set; + + // Populate the disjoint set with ONLY non joints that are in the skeleton hierarchy (non_joints vector) + // This way we can find any joints that lie in between joints, as the current glTF specification + // mentions nothing about non-joints being in between joints of the same skin. Hopefully one day we + // can remove this code. + + // skinD depicted here explains this issue: + // https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Animation_Skin + + for (int i = 0; i < non_joints.size(); ++i) { + const GLTFNodeIndex node_i = non_joints[i]; + + subtree_set.insert(node_i); + + const GLTFNodeIndex parent_i = state->nodes[node_i]->parent; + if (parent_i >= 0 && non_joints.find(parent_i) >= 0 && !state->nodes[parent_i]->joint) { + subtree_set.create_union(parent_i, node_i); + } + } + + // Find all the non joint subtrees and re-parent them to a new "fake" joint + + Vector<GLTFNodeIndex> non_joint_subtree_roots; + subtree_set.get_representatives(non_joint_subtree_roots); + + for (int root_i = 0; root_i < non_joint_subtree_roots.size(); ++root_i) { + const GLTFNodeIndex subtree_root = non_joint_subtree_roots[root_i]; + + Vector<GLTFNodeIndex> subtree_nodes; + subtree_set.get_members(subtree_nodes, subtree_root); + + for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) { + ERR_FAIL_COND_V(_reparent_to_fake_joint(state, skeleton, subtree_nodes[subtree_i]), FAILED); + + // We modified the tree, recompute all the heights + _compute_node_heights(state); + } + } + + return OK; +} + +Error GLTFDocument::_reparent_to_fake_joint(Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton, const GLTFNodeIndex node_index) { + Ref<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->set_name(node->get_name()); + + // Clear the nodes transforms, since it will be parented to the fake joint + node->translation = Vector3(0, 0, 0); + node->rotation = Quat(); + node->scale = Vector3(1, 1, 1); + node->xform = Transform(); + + // Transfer the node children to the fake joint + for (int child_i = 0; child_i < node->children.size(); ++child_i) { + Ref<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) { + Ref<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) { + Ref<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(Ref<GLTFState> state, const GLTFSkeletonIndex skel_i) { + DisjointSet<GLTFNodeIndex> disjoint_set; + + for (GLTFNodeIndex i = 0; i < state->nodes.size(); ++i) { + const Ref<GLTFNode> node = state->nodes[i]; + + if (node->skeleton != skel_i) { + continue; + } + + disjoint_set.insert(i); + + if (node->parent >= 0 && state->nodes[node->parent]->skeleton == skel_i) { + disjoint_set.create_union(node->parent, i); + } + } + + Ref<GLTFSkeleton> skeleton = state->skeletons.write[skel_i]; + + Vector<GLTFNodeIndex> owners; + disjoint_set.get_representatives(owners); + + Vector<GLTFNodeIndex> roots; + + for (int i = 0; i < owners.size(); ++i) { + Vector<GLTFNodeIndex> set; + disjoint_set.get_members(set, owners[i]); + const GLTFNodeIndex root = _find_highest_node(state, set); + ERR_FAIL_COND_V(root < 0, FAILED); + roots.push_back(root); + } + + roots.sort(); + + 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(Ref<GLTFState> state) { + for (GLTFSkeletonIndex skel_i = 0; skel_i < state->skeletons.size(); ++skel_i) { + Ref<GLTFSkeleton> gltf_skeleton = state->skeletons.write[skel_i]; + + Skeleton3D *skeleton = memnew(Skeleton3D); + gltf_skeleton->godot_skeleton = skeleton; + + // Make a unique name, no gltf node represents this skeleton + skeleton->set_name(_gen_unique_name(state, "Skeleton3D")); + + List<GLTFNodeIndex> bones; + + for (int i = 0; i < gltf_skeleton->roots.size(); ++i) { + bones.push_back(gltf_skeleton->roots[i]); + } + + // Make the skeleton creation deterministic by going through the roots in + // a sorted order, and DEPTH FIRST + bones.sort(); + + while (!bones.is_empty()) { + const GLTFNodeIndex node_i = bones.front()->get(); + bones.pop_front(); + + Ref<GLTFNode> node = state->nodes[node_i]; + ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED); + + { // Add all child nodes to the stack (deterministically) + Vector<GLTFNodeIndex> child_nodes; + for (int i = 0; i < node->children.size(); ++i) { + const GLTFNodeIndex child_i = node->children[i]; + if (state->nodes[child_i]->skeleton == skel_i) { + child_nodes.push_back(child_i); + } + } + + // Depth first insertion + child_nodes.sort(); + for (int i = child_nodes.size() - 1; i >= 0; --i) { + bones.push_front(child_nodes[i]); + } + } + + const int bone_index = skeleton->get_bone_count(); + + if (node->get_name().is_empty()) { + node->set_name("bone"); + } + + node->set_name(_gen_unique_bone_name(state, skel_i, node->get_name())); + + skeleton->add_bone(node->get_name()); + skeleton->set_bone_rest(bone_index, node->xform); + + if (node->parent >= 0 && state->nodes[node->parent]->skeleton == skel_i) { + const int bone_parent = skeleton->find_bone(state->nodes[node->parent]->get_name()); + ERR_FAIL_COND_V(bone_parent < 0, FAILED); + skeleton->set_bone_parent(bone_index, skeleton->find_bone(state->nodes[node->parent]->get_name())); + } + + state->scene_nodes.insert(node_i, skeleton); + } + } + + ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(state), ERR_PARSE_ERROR); + + return OK; +} + +Error GLTFDocument::_map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFState> state) { + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref<GLTFSkin> skin = state->skins.write[skin_i]; + + Ref<GLTFSkeleton> skeleton = state->skeletons[skin->skeleton]; + + for (int joint_index = 0; joint_index < skin->joints_original.size(); ++joint_index) { + const GLTFNodeIndex node_i = skin->joints_original[joint_index]; + const Ref<GLTFNode> node = state->nodes[node_i]; + + const int bone_index = skeleton->godot_skeleton->find_bone(node->get_name()); + ERR_FAIL_COND_V(bone_index < 0, FAILED); + + skin->joint_i_to_bone_i.insert(joint_index, bone_index); + } + } + + return OK; +} + +Error GLTFDocument::_serialize_skins(Ref<GLTFState> state) { + _remove_duplicate_skins(state); + return OK; +} + +Error GLTFDocument::_create_skins(Ref<GLTFState> state) { + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref<GLTFSkin> gltf_skin = state->skins.write[skin_i]; + + Ref<Skin> skin; + skin.instance(); + + // Some skins don't have IBM's! What absolute monsters! + const bool has_ibms = !gltf_skin->inverse_binds.is_empty(); + + for (int joint_i = 0; joint_i < gltf_skin->joints_original.size(); ++joint_i) { + GLTFNodeIndex node = gltf_skin->joints_original[joint_i]; + String bone_name = state->nodes[node]->get_name(); + + Transform xform; + if (has_ibms) { + xform = gltf_skin->inverse_binds[joint_i]; + } + + if (state->use_named_skin_binds) { + skin->add_named_bind(bone_name, xform); + } else { + int32_t bone_i = gltf_skin->joint_i_to_bone_i[joint_i]; + skin->add_bind(bone_i, xform); + } + } + + gltf_skin->godot_skin = skin; + } + + // Purge the duplicates! + _remove_duplicate_skins(state); + + // Create unique names now, after removing duplicates + for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { + Ref<Skin> skin = state->skins.write[skin_i]->godot_skin; + if (skin->get_name().is_empty()) { + // Make a unique name, no gltf node represents this skin + skin->set_name(_gen_unique_name(state, "Skin")); + } + } + + return OK; +} + +bool GLTFDocument::_skins_are_same(const Ref<Skin> skin_a, const Ref<Skin> skin_b) { + if (skin_a->get_bind_count() != skin_b->get_bind_count()) { + return false; + } + + for (int i = 0; i < skin_a->get_bind_count(); ++i) { + if (skin_a->get_bind_bone(i) != skin_b->get_bind_bone(i)) { + return false; + } + + Transform a_xform = skin_a->get_bind_pose(i); + Transform b_xform = skin_b->get_bind_pose(i); + + if (a_xform != b_xform) { + return false; + } + } + + return true; +} + +void GLTFDocument::_remove_duplicate_skins(Ref<GLTFState> state) { + for (int i = 0; i < state->skins.size(); ++i) { + for (int j = i + 1; j < state->skins.size(); ++j) { + const Ref<Skin> skin_i = state->skins[i]->godot_skin; + const Ref<Skin> skin_j = state->skins[j]->godot_skin; + + if (_skins_are_same(skin_i, skin_j)) { + // replace it and delete the old + state->skins.write[j]->godot_skin = skin_i; + } + } + } +} + +Error GLTFDocument::_serialize_lights(Ref<GLTFState> state) { + Array lights; + for (GLTFLightIndex i = 0; i < state->lights.size(); i++) { + Dictionary d; + Ref<GLTFLight> light = state->lights[i]; + Array color; + color.resize(3); + color[0] = light->color.r; + color[1] = light->color.g; + color[2] = light->color.b; + d["color"] = color; + d["type"] = light->type; + if (light->type == "spot") { + Dictionary s; + float inner_cone_angle = light->inner_cone_angle; + s["innerConeAngle"] = inner_cone_angle; + float outer_cone_angle = light->outer_cone_angle; + s["outerConeAngle"] = outer_cone_angle; + d["spot"] = s; + } + float intensity = light->intensity; + d["intensity"] = intensity; + float range = light->range; + d["range"] = range; + lights.push_back(d); + } + + if (!state->lights.size()) { + return OK; + } + + Dictionary extensions; + if (state->json.has("extensions")) { + extensions = state->json["extensions"]; + } else { + state->json["extensions"] = extensions; + } + Dictionary lights_punctual; + extensions["KHR_lights_punctual"] = lights_punctual; + lights_punctual["lights"] = lights; + + print_verbose("glTF: Total lights: " + itos(state->lights.size())); + + return OK; +} + +Error GLTFDocument::_serialize_cameras(Ref<GLTFState> state) { + Array cameras; + cameras.resize(state->cameras.size()); + for (GLTFCameraIndex i = 0; i < state->cameras.size(); i++) { + Dictionary d; + + Ref<GLTFCamera> camera = state->cameras[i]; + + if (camera->get_perspective() == false) { + Dictionary og; + og["ymag"] = Math::deg2rad(camera->get_fov_size()); + og["xmag"] = Math::deg2rad(camera->get_fov_size()); + og["zfar"] = camera->get_zfar(); + og["znear"] = camera->get_znear(); + d["orthographic"] = og; + d["type"] = "orthographic"; + } else if (camera->get_perspective()) { + Dictionary ppt; + // GLTF spec is in radians, Godot's camera is in degrees. + ppt["yfov"] = Math::deg2rad(camera->get_fov_size()); + ppt["zfar"] = camera->get_zfar(); + ppt["znear"] = camera->get_znear(); + d["perspective"] = ppt; + d["type"] = "perspective"; + } + cameras[i] = d; + } + + if (!state->cameras.size()) { + return OK; + } + + state->json["cameras"] = cameras; + + print_verbose("glTF: Total cameras: " + itos(state->cameras.size())); + + return OK; +} + +Error GLTFDocument::_parse_lights(Ref<GLTFState> state) { + if (!state->json.has("extensions")) { + return OK; + } + Dictionary extensions = state->json["extensions"]; + if (!extensions.has("KHR_lights_punctual")) { + return OK; + } + Dictionary lights_punctual = extensions["KHR_lights_punctual"]; + if (!lights_punctual.has("lights")) { + return OK; + } + + const Array &lights = lights_punctual["lights"]; + + for (GLTFLightIndex light_i = 0; light_i < lights.size(); light_i++) { + const Dictionary &d = lights[light_i]; + + Ref<GLTFLight> light; + light.instance(); + ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); + const String &type = d["type"]; + light->type = type; + + if (d.has("color")) { + const Array &arr = d["color"]; + ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); + light->color = c; + } + if (d.has("intensity")) { + light->intensity = d["intensity"]; + } + if (d.has("range")) { + light->range = d["range"]; + } + if (type == "spot") { + const Dictionary &spot = d["spot"]; + light->inner_cone_angle = spot["innerConeAngle"]; + light->outer_cone_angle = spot["outerConeAngle"]; + ERR_FAIL_COND_V_MSG(light->inner_cone_angle >= light->outer_cone_angle, ERR_PARSE_ERROR, "The inner angle must be smaller than the outer angle."); + } else if (type != "point" && type != "directional") { + ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Light type is unknown."); + } + + state->lights.push_back(light); + } + + print_verbose("glTF: Total lights: " + itos(state->lights.size())); + + return OK; +} + +Error GLTFDocument::_parse_cameras(Ref<GLTFState> state) { + if (!state->json.has("cameras")) + return OK; + + const Array cameras = state->json["cameras"]; + + for (GLTFCameraIndex i = 0; i < cameras.size(); i++) { + const Dictionary &d = cameras[i]; + + Ref<GLTFCamera> camera; + camera.instance(); + ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); + const String &type = d["type"]; + if (type == "orthographic") { + camera->set_perspective(false); + if (d.has("orthographic")) { + const Dictionary &og = d["orthographic"]; + // GLTF spec is in radians, Godot's camera is in degrees. + camera->set_fov_size(Math::rad2deg(real_t(og["ymag"]))); + camera->set_zfar(og["zfar"]); + camera->set_znear(og["znear"]); + } else { + camera->set_fov_size(10); + } + } else if (type == "perspective") { + camera->set_perspective(true); + if (d.has("perspective")) { + const Dictionary &ppt = d["perspective"]; + // GLTF spec is in radians, Godot's camera is in degrees. + camera->set_fov_size(Math::rad2deg(real_t(ppt["yfov"]))); + camera->set_zfar(ppt["zfar"]); + camera->set_znear(ppt["znear"]); + } else { + camera->set_fov_size(10); + } + } else { + ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Camera3D should be in 'orthographic' or 'perspective'"); + } + + state->cameras.push_back(camera); + } + + print_verbose("glTF: Total cameras: " + itos(state->cameras.size())); + + return OK; +} + +String GLTFDocument::interpolation_to_string(const GLTFAnimation::Interpolation p_interp) { + String interp = "LINEAR"; + if (p_interp == GLTFAnimation::INTERP_STEP) { + interp = "STEP"; + } else if (p_interp == GLTFAnimation::INTERP_LINEAR) { + interp = "LINEAR"; + } else if (p_interp == GLTFAnimation::INTERP_CATMULLROMSPLINE) { + interp = "CATMULLROMSPLINE"; + } else if (p_interp == GLTFAnimation::INTERP_CUBIC_SPLINE) { + interp = "CUBICSPLINE"; + } + + return interp; +} + +Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) { + if (!state->animation_players.size()) { + return OK; + } + for (int32_t player_i = 0; player_i < state->animation_players.size(); player_i++) { + List<StringName> animation_names; + AnimationPlayer *animation_player = state->animation_players[player_i]; + animation_player->get_animation_list(&animation_names); + if (animation_names.size()) { + for (int animation_name_i = 0; animation_name_i < animation_names.size(); animation_name_i++) { + _convert_animation(state, animation_player, animation_names[animation_name_i]); + } + } + } + Array animations; + for (GLTFAnimationIndex animation_i = 0; animation_i < state->animations.size(); animation_i++) { + Dictionary d; + Ref<GLTFAnimation> gltf_animation = state->animations[animation_i]; + if (!gltf_animation->get_tracks().size()) { + continue; + } + + if (!gltf_animation->get_name().is_empty()) { + d["name"] = gltf_animation->get_name(); + } + Array channels; + Array samplers; + + for (Map<int, GLTFAnimation::Track>::Element *track_i = gltf_animation->get_tracks().front(); track_i; track_i = track_i->next()) { + GLTFAnimation::Track track = track_i->get(); + if (track.translation_track.times.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + s["interpolation"] = interpolation_to_string(track.translation_track.interpolation); + Vector<real_t> times = Variant(track.translation_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Vector<Vector3> values = Variant(track.translation_track.values); + s["output"] = _encode_accessor_as_vec3(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "translation"; + target["node"] = track_i->key(); + + t["target"] = target; + channels.push_back(t); + } + if (track.rotation_track.times.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + s["interpolation"] = interpolation_to_string(track.rotation_track.interpolation); + Vector<real_t> times = Variant(track.rotation_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Vector<Quat> values = track.rotation_track.values; + s["output"] = _encode_accessor_as_quats(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "rotation"; + target["node"] = track_i->key(); + + t["target"] = target; + channels.push_back(t); + } + if (track.scale_track.times.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + s["interpolation"] = interpolation_to_string(track.scale_track.interpolation); + Vector<real_t> times = Variant(track.scale_track.times); + s["input"] = _encode_accessor_as_floats(state, times, false); + Vector<Vector3> values = Variant(track.scale_track.values); + s["output"] = _encode_accessor_as_vec3(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "scale"; + target["node"] = track_i->key(); + + t["target"] = target; + channels.push_back(t); + } + if (track.weight_tracks.size()) { + Dictionary t; + t["sampler"] = samplers.size(); + Dictionary s; + + Vector<real_t> times; + Vector<real_t> values; + + for (int32_t times_i = 0; times_i < track.weight_tracks[0].times.size(); times_i++) { + real_t time = track.weight_tracks[0].times[times_i]; + times.push_back(time); + } + + values.resize(times.size() * track.weight_tracks.size()); + // TODO Sort by order in blend shapes + for (int k = 0; k < track.weight_tracks.size(); k++) { + Vector<float> wdata = track.weight_tracks[k].values; + for (int l = 0; l < wdata.size(); l++) { + values.write[l * track.weight_tracks.size() + k] = wdata.write[l]; + } + } + + s["interpolation"] = interpolation_to_string(track.weight_tracks[track.weight_tracks.size() - 1].interpolation); + s["input"] = _encode_accessor_as_floats(state, times, false); + s["output"] = _encode_accessor_as_floats(state, values, false); + + samplers.push_back(s); + + Dictionary target; + target["path"] = "weights"; + target["node"] = track_i->key(); + + t["target"] = target; + channels.push_back(t); + } + } + if (channels.size() && samplers.size()) { + d["channels"] = channels; + d["samplers"] = samplers; + animations.push_back(d); + } + } + + state->json["animations"] = animations; + + print_verbose("glTF: Total animations '" + itos(state->animations.size()) + "'."); + + return OK; +} + +Error GLTFDocument::_parse_animations(Ref<GLTFState> state) { + if (!state->json.has("animations")) + return OK; + + const Array &animations = state->json["animations"]; + + for (GLTFAnimationIndex i = 0; i < animations.size(); i++) { + const Dictionary &d = animations[i]; + + Ref<GLTFAnimation> animation; + animation.instance(); + + if (!d.has("channels") || !d.has("samplers")) + continue; + + Array channels = d["channels"]; + Array samplers = d["samplers"]; + + if (d.has("name")) { + const String name = d["name"]; + if (name.begins_with("loop") || name.ends_with("loop") || name.begins_with("cycle") || name.ends_with("cycle")) { + animation->set_loop(true); + } + animation->set_name(_sanitize_scene_name(name)); + } + + for (int j = 0; j < channels.size(); j++) { + const Dictionary &c = channels[j]; + if (!c.has("target")) + continue; + + const Dictionary &t = c["target"]; + if (!t.has("node") || !t.has("path")) { + continue; + } + + ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR); + const int sampler = c["sampler"]; + ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR); + + GLTFNodeIndex node = t["node"]; + String path = t["path"]; + + ERR_FAIL_INDEX_V(node, state->nodes.size(), ERR_PARSE_ERROR); + + GLTFAnimation::Track *track = nullptr; + + if (!animation->get_tracks().has(node)) { + animation->get_tracks()[node] = GLTFAnimation::Track(); + } + + track = &animation->get_tracks()[node]; + + const Dictionary &s = samplers[sampler]; + + ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR); + + const int input = s["input"]; + const int output = s["output"]; + + GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR; + int output_count = 1; + if (s.has("interpolation")) { + const String &in = s["interpolation"]; + if (in == "STEP") { + interp = GLTFAnimation::INTERP_STEP; + } else if (in == "LINEAR") { + interp = GLTFAnimation::INTERP_LINEAR; + } else if (in == "CATMULLROMSPLINE") { + interp = GLTFAnimation::INTERP_CATMULLROMSPLINE; + output_count = 3; + } else if (in == "CUBICSPLINE") { + interp = GLTFAnimation::INTERP_CUBIC_SPLINE; + output_count = 3; + } + } + + const Vector<float> times = _decode_accessor_as_floats(state, input, false); + if (path == "translation") { + const Vector<Vector3> translations = _decode_accessor_as_vec3(state, output, false); + track->translation_track.interpolation = interp; + track->translation_track.times = Variant(times); //convert via variant + track->translation_track.values = Variant(translations); //convert via variant + } else if (path == "rotation") { + const Vector<Quat> rotations = _decode_accessor_as_quat(state, output, false); + track->rotation_track.interpolation = interp; + track->rotation_track.times = Variant(times); //convert via variant + track->rotation_track.values = rotations; + } else if (path == "scale") { + const Vector<Vector3> scales = _decode_accessor_as_vec3(state, output, false); + track->scale_track.interpolation = interp; + track->scale_track.times = Variant(times); //convert via variant + track->scale_track.values = Variant(scales); //convert via variant + } else if (path == "weights") { + const Vector<float> weights = _decode_accessor_as_floats(state, output, false); + + ERR_FAIL_INDEX_V(state->nodes[node]->mesh, state->meshes.size(), ERR_PARSE_ERROR); + Ref<GLTFMesh> mesh = state->meshes[state->nodes[node]->mesh]; + ERR_CONTINUE(!mesh->get_blend_weights().size()); + const int wc = mesh->get_blend_weights().size(); + + track->weight_tracks.resize(wc); + + const int expected_value_count = times.size() * output_count * wc; + ERR_FAIL_COND_V_MSG(weights.size() != expected_value_count, ERR_PARSE_ERROR, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead."); + + const int wlen = weights.size() / wc; + for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea + GLTFAnimation::Channel<float> cf; + cf.interpolation = interp; + cf.times = Variant(times); + Vector<float> wdata; + wdata.resize(wlen); + for (int l = 0; l < wlen; l++) { + wdata.write[l] = weights[l * wc + k]; + } + + cf.values = wdata; + track->weight_tracks.write[k] = cf; + } + } else { + WARN_PRINT("Invalid path '" + path + "'."); + } + } + + state->animations.push_back(animation); + } + + print_verbose("glTF: Total animations '" + itos(state->animations.size()) + "'."); + + return OK; +} + +void GLTFDocument::_assign_scene_names(Ref<GLTFState> state) { + for (int i = 0; i < state->nodes.size(); i++) { + Ref<GLTFNode> n = state->nodes[i]; + + // Any joints get unique names generated when the skeleton is made, unique to the skeleton + if (n->skeleton >= 0) + continue; + + if (n->get_name().is_empty()) { + if (n->mesh >= 0) { + n->set_name(_gen_unique_name(state, "Mesh")); + } else if (n->camera >= 0) { + n->set_name(_gen_unique_name(state, "Camera3D")); + } else { + n->set_name(_gen_unique_name(state, "Node")); + } + } + + n->set_name(_gen_unique_name(state, n->get_name())); + } +} + +BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> state, Skeleton3D *skeleton, const GLTFNodeIndex node_index) { + Ref<GLTFNode> gltf_node = state->nodes[node_index]; + Ref<GLTFNode> bone_node = state->nodes[gltf_node->parent]; + + BoneAttachment3D *bone_attachment = memnew(BoneAttachment3D); + print_verbose("glTF: Creating bone attachment for: " + gltf_node->get_name()); + + ERR_FAIL_COND_V(!bone_node->joint, nullptr); + + bone_attachment->set_bone_name(bone_node->get_name()); + + return bone_attachment; +} + +GLTFMeshIndex GLTFDocument::_convert_mesh_instance(Ref<GLTFState> state, MeshInstance3D *p_mesh_instance) { + ERR_FAIL_NULL_V(p_mesh_instance, -1); + if (p_mesh_instance->get_mesh().is_null()) { + return -1; + } + Ref<EditorSceneImporterMesh> import_mesh; + import_mesh.instance(); + Ref<Mesh> godot_mesh = p_mesh_instance->get_mesh(); + if (godot_mesh.is_null()) { + return -1; + } + Vector<float> blend_weights; + Vector<String> blend_names; + int32_t blend_count = godot_mesh->get_blend_shape_count(); + blend_names.resize(blend_count); + blend_weights.resize(blend_count); + for (int32_t blend_i = 0; blend_i < godot_mesh->get_blend_shape_count(); blend_i++) { + String blend_name = godot_mesh->get_blend_shape_name(blend_i); + blend_names.write[blend_i] = blend_name; + import_mesh->add_blend_shape(blend_name); + } + for (int32_t surface_i = 0; surface_i < godot_mesh->get_surface_count(); surface_i++) { + Mesh::PrimitiveType primitive_type = godot_mesh->surface_get_primitive_type(surface_i); + Array arrays = godot_mesh->surface_get_arrays(surface_i); + Array blend_shape_arrays = godot_mesh->surface_get_blend_shape_arrays(surface_i); + Ref<Material> mat = godot_mesh->surface_get_material(surface_i); + Ref<ArrayMesh> godot_array_mesh = godot_mesh; + String surface_name; + if (godot_array_mesh.is_valid()) { + surface_name = godot_array_mesh->surface_get_name(surface_i); + } + if (p_mesh_instance->get_surface_material(surface_i).is_valid()) { + mat = p_mesh_instance->get_surface_material(surface_i); + } + if (p_mesh_instance->get_material_override().is_valid()) { + mat = p_mesh_instance->get_material_override(); + } + import_mesh->add_surface(primitive_type, arrays, blend_shape_arrays, Dictionary(), mat, surface_name); + } + for (int32_t blend_i = 0; blend_i < blend_count; blend_i++) { + blend_weights.write[blend_i] = 0.0f; + } + Ref<GLTFMesh> gltf_mesh; + gltf_mesh.instance(); + gltf_mesh->set_mesh(import_mesh); + gltf_mesh->set_blend_weights(blend_weights); + GLTFMeshIndex mesh_i = state->meshes.size(); + state->meshes.push_back(gltf_mesh); + return mesh_i; +} + +EditorSceneImporterMeshNode3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) { + Ref<GLTFNode> gltf_node = state->nodes[node_index]; + + ERR_FAIL_INDEX_V(gltf_node->mesh, state->meshes.size(), nullptr); + + EditorSceneImporterMeshNode3D *mi = memnew(EditorSceneImporterMeshNode3D); + print_verbose("glTF: Creating mesh for: " + gltf_node->get_name()); + + Ref<GLTFMesh> mesh = state->meshes.write[gltf_node->mesh]; + if (mesh.is_null()) { + return mi; + } + Ref<EditorSceneImporterMesh> import_mesh = mesh->get_mesh(); + if (import_mesh.is_null()) { + return mi; + } + mi->set_mesh(import_mesh); + for (int i = 0; i < mesh->get_blend_weights().size(); i++) { + mi->set("blend_shapes/" + mesh->get_mesh()->get_blend_shape_name(i), mesh->get_blend_weights()[i]); + } + return mi; +} + +Light3D *GLTFDocument::_generate_light(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) { + Ref<GLTFNode> gltf_node = state->nodes[node_index]; + + ERR_FAIL_INDEX_V(gltf_node->light, state->lights.size(), nullptr); + + print_verbose("glTF: Creating light for: " + gltf_node->get_name()); + + Ref<GLTFLight> l = state->lights[gltf_node->light]; + + float intensity = l->intensity; + if (intensity > 10) { + // GLTF spec has the default around 1, but Blender defaults lights to 100. + // The only sane way to handle this is to check where it came from and + // handle it accordingly. If it's over 10, it probably came from Blender. + intensity /= 100; + } + + if (l->type == "directional") { + DirectionalLight3D *light = memnew(DirectionalLight3D); + light->set_param(Light3D::PARAM_ENERGY, intensity); + light->set_color(l->color); + return light; + } + + const float range = CLAMP(l->range, 0, 4096); + // Doubling the range will double the effective brightness, so we need double attenuation (half brightness). + // We want to have double intensity give double brightness, so we need half the attenuation. + const float attenuation = range / intensity; + if (l->type == "point") { + OmniLight3D *light = memnew(OmniLight3D); + light->set_param(OmniLight3D::PARAM_ATTENUATION, attenuation); + light->set_param(OmniLight3D::PARAM_RANGE, range); + light->set_color(l->color); + return light; + } + if (l->type == "spot") { + SpotLight3D *light = memnew(SpotLight3D); + light->set_param(SpotLight3D::PARAM_ATTENUATION, attenuation); + light->set_param(SpotLight3D::PARAM_RANGE, range); + light->set_param(SpotLight3D::PARAM_SPOT_ANGLE, Math::rad2deg(l->outer_cone_angle)); + light->set_color(l->color); + + // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b + // The points in desmos are not exact, except for (1, infinity). + float angle_ratio = l->inner_cone_angle / l->outer_cone_angle; + float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1; + light->set_param(SpotLight3D::PARAM_SPOT_ATTENUATION, angle_attenuation); + return light; + } + return nullptr; +} + +Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) { + Ref<GLTFNode> gltf_node = state->nodes[node_index]; + + ERR_FAIL_INDEX_V(gltf_node->camera, state->cameras.size(), nullptr); + + Camera3D *camera = memnew(Camera3D); + print_verbose("glTF: Creating camera for: " + gltf_node->get_name()); + + Ref<GLTFCamera> c = state->cameras[gltf_node->camera]; + if (c->get_perspective()) { + camera->set_perspective(c->get_fov_size(), c->get_znear(), c->get_zfar()); + } else { + camera->set_orthogonal(c->get_fov_size(), c->get_znear(), c->get_zfar()); + } + + return camera; +} + +GLTFCameraIndex GLTFDocument::_convert_camera(Ref<GLTFState> state, Camera3D *p_camera) { + print_verbose("glTF: Converting camera: " + p_camera->get_name()); + + Ref<GLTFCamera> c; + c.instance(); + + if (p_camera->get_projection() == Camera3D::Projection::PROJECTION_PERSPECTIVE) { + c->set_perspective(true); + c->set_fov_size(p_camera->get_fov()); + c->set_zfar(p_camera->get_far()); + c->set_znear(p_camera->get_near()); + } else { + c->set_fov_size(p_camera->get_fov()); + c->set_zfar(p_camera->get_far()); + c->set_znear(p_camera->get_near()); + } + GLTFCameraIndex camera_index = state->cameras.size(); + state->cameras.push_back(c); + return camera_index; +} + +GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> state, Light3D *p_light) { + print_verbose("glTF: Converting light: " + p_light->get_name()); + + Ref<GLTFLight> l; + l.instance(); + l->color = p_light->get_color(); + if (cast_to<DirectionalLight3D>(p_light)) { + l->type = "directional"; + DirectionalLight3D *light = cast_to<DirectionalLight3D>(p_light); + l->intensity = light->get_param(DirectionalLight3D::PARAM_ENERGY); + l->range = FLT_MAX; // Range for directional lights is infinite in Godot. + } else if (cast_to<OmniLight3D>(p_light)) { + l->type = "point"; + OmniLight3D *light = cast_to<OmniLight3D>(p_light); + l->range = light->get_param(OmniLight3D::PARAM_RANGE); + float attenuation = p_light->get_param(OmniLight3D::PARAM_ATTENUATION); + l->intensity = l->range / attenuation; + } else if (cast_to<SpotLight3D>(p_light)) { + l->type = "spot"; + SpotLight3D *light = cast_to<SpotLight3D>(p_light); + l->range = light->get_param(SpotLight3D::PARAM_RANGE); + float attenuation = light->get_param(SpotLight3D::PARAM_ATTENUATION); + l->intensity = l->range / attenuation; + l->outer_cone_angle = Math::deg2rad(light->get_param(SpotLight3D::PARAM_SPOT_ANGLE)); + + // This equation is the inverse of the import equation (which has a desmos link). + float angle_ratio = 1 - (0.2 / (0.1 + light->get_param(SpotLight3D::PARAM_SPOT_ATTENUATION))); + angle_ratio = MAX(0, angle_ratio); + l->inner_cone_angle = l->outer_cone_angle * angle_ratio; + } + + GLTFLightIndex light_index = state->lights.size(); + state->lights.push_back(l); + return light_index; +} + +GLTFSkeletonIndex GLTFDocument::_convert_skeleton(Ref<GLTFState> state, Skeleton3D *p_skeleton) { + print_verbose("glTF: Converting skeleton: " + p_skeleton->get_name()); + Ref<GLTFSkeleton> gltf_skeleton; + gltf_skeleton.instance(); + gltf_skeleton->set_name(_gen_unique_name(state, p_skeleton->get_name())); + gltf_skeleton->godot_skeleton = p_skeleton; + GLTFSkeletonIndex skeleton_i = state->skeletons.size(); + state->skeletons.push_back(gltf_skeleton); + return skeleton_i; +} + +void GLTFDocument::_convert_spatial(Ref<GLTFState> state, Node3D *p_spatial, Ref<GLTFNode> p_node) { + Transform xform = p_spatial->get_transform(); + p_node->scale = xform.basis.get_scale(); + p_node->rotation = xform.basis.get_rotation_quat(); + p_node->translation = xform.origin; +} + +Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) { + Ref<GLTFNode> gltf_node = state->nodes[node_index]; + + Node3D *spatial = memnew(Node3D); + print_verbose("glTF: Converting spatial: " + gltf_node->get_name()); + + return spatial; +} +void GLTFDocument::_convert_scene_node(Ref<GLTFState> state, Node *p_current, Node *p_root, const GLTFNodeIndex p_gltf_parent, const GLTFNodeIndex p_gltf_root) { + bool retflag = true; + Node3D *spatial = cast_to<Node3D>(p_current); + _check_visibility(p_current, retflag); + if (retflag) { + return; + } + Ref<GLTFNode> gltf_node; + gltf_node.instance(); + gltf_node->set_name(_gen_unique_name(state, p_current->get_name())); + if (cast_to<Node3D>(p_current)) { + _convert_spatial(state, spatial, gltf_node); + } + if (cast_to<MeshInstance3D>(p_current)) { + _convert_mesh_to_gltf(p_current, state, spatial, gltf_node); + } else if (cast_to<BoneAttachment3D>(p_current)) { + _convert_bone_attachment_to_gltf(p_current, state, gltf_node, retflag); + // TODO 2020-12-21 iFire Handle the case of objects under the bone attachment. + return; + } else if (cast_to<Skeleton3D>(p_current)) { + _convert_skeleton_to_gltf(p_current, state, p_gltf_parent, p_gltf_root, gltf_node, p_root); + // We ignore the Godot Engine node that is the skeleton. + return; + } else if (cast_to<MultiMeshInstance3D>(p_current)) { + _convert_mult_mesh_instance_to_gltf(p_current, p_gltf_parent, p_gltf_root, gltf_node, state, p_root); + } else if (cast_to<CSGShape3D>(p_current)) { + if (p_current->get_parent() && cast_to<CSGShape3D>(p_current)->is_root_shape()) { + _convert_csg_shape_to_gltf(p_current, p_gltf_parent, gltf_node, state); + } + } else if (cast_to<GridMap>(p_current)) { + _convert_grid_map_to_gltf(p_current, p_gltf_parent, p_gltf_root, gltf_node, state, p_root); + } else if (cast_to<Camera3D>(p_current)) { + Camera3D *camera = Object::cast_to<Camera3D>(p_current); + _convert_camera_to_gltf(camera, state, spatial, gltf_node); + } else if (cast_to<Light3D>(p_current)) { + Light3D *light = Object::cast_to<Light3D>(p_current); + _convert_light_to_gltf(light, state, spatial, gltf_node); + } else if (cast_to<AnimationPlayer>(p_current)) { + AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(p_current); + _convert_animation_player_to_gltf(animation_player, state, p_gltf_parent, p_gltf_root, gltf_node, p_current, p_root); + } + GLTFNodeIndex current_node_i = state->nodes.size(); + GLTFNodeIndex gltf_root = p_gltf_root; + if (gltf_root == -1) { + gltf_root = current_node_i; + Array scenes; + scenes.push_back(gltf_root); + state->json["scene"] = scenes; + } + _create_gltf_node(state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node); + for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) { + _convert_scene_node(state, p_current->get_child(node_i), p_root, current_node_i, gltf_root); + } +} + +void GLTFDocument::_convert_csg_shape_to_gltf(Node *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> gltf_node, Ref<GLTFState> state) { + CSGShape3D *csg = Object::cast_to<CSGShape3D>(p_current); + csg->call("_update_shape"); + Array meshes = csg->get_meshes(); + if (meshes.size() != 2) { + return; + } + Ref<Material> mat; + if (csg->get_material_override().is_valid()) { + mat = csg->get_material_override(); + } + Ref<GLTFMesh> gltf_mesh; + gltf_mesh.instance(); + Ref<EditorSceneImporterMesh> import_mesh; + import_mesh.instance(); + Ref<ArrayMesh> array_mesh = csg->get_meshes()[1]; + for (int32_t surface_i = 0; surface_i < array_mesh->get_surface_count(); surface_i++) { + import_mesh->add_surface(Mesh::PrimitiveType::PRIMITIVE_TRIANGLES, array_mesh->surface_get_arrays(surface_i), Array(), Dictionary(), mat, array_mesh->surface_get_name(surface_i)); + } + gltf_mesh->set_mesh(import_mesh); + GLTFMeshIndex mesh_i = state->meshes.size(); + state->meshes.push_back(gltf_mesh); + gltf_node->mesh = mesh_i; + gltf_node->xform = csg->get_meshes()[0]; + gltf_node->set_name(_gen_unique_name(state, csg->get_name())); +} + +void GLTFDocument::_create_gltf_node(Ref<GLTFState> state, Node *p_scene_parent, GLTFNodeIndex current_node_i, + GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, Ref<GLTFNode> gltf_node) { + state->scene_nodes.insert(current_node_i, p_scene_parent); + state->nodes.push_back(gltf_node); + if (current_node_i == p_parent_node_index) { + return; + } + if (p_parent_node_index == -1) { + return; + } + state->nodes.write[p_parent_node_index]->children.push_back(current_node_i); +} + +void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *animation_player, Ref<GLTFState> state, const GLTFNodeIndex &p_gltf_current, const GLTFNodeIndex &p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent, Node *p_root) { + ERR_FAIL_COND(!animation_player); + state->animation_players.push_back(animation_player); + print_verbose(String("glTF: Converting animation player: ") + animation_player->get_name()); +} + +void GLTFDocument::_check_visibility(Node *p_node, bool &retflag) { + retflag = true; + Node3D *spatial = Object::cast_to<Node3D>(p_node); + Node2D *node_2d = Object::cast_to<Node2D>(p_node); + if (node_2d && !node_2d->is_visible()) { + return; + } + if (spatial && !spatial->is_visible()) { + return; + } + retflag = false; +} + +void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> state, Node3D *spatial, Ref<GLTFNode> gltf_node) { + ERR_FAIL_COND(!camera); + GLTFCameraIndex camera_index = _convert_camera(state, camera); + if (camera_index != -1) { + gltf_node->camera = camera_index; + } +} + +void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Node3D *spatial, Ref<GLTFNode> gltf_node) { + ERR_FAIL_COND(!light); + GLTFLightIndex light_index = _convert_light(state, light); + if (light_index != -1) { + gltf_node->light = light_index; + } +} + +void GLTFDocument::_convert_grid_map_to_gltf(Node *p_scene_parent, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref<GLTFNode> gltf_node, Ref<GLTFState> state, Node *p_root_node) { + GridMap *grid_map = Object::cast_to<GridMap>(p_scene_parent); + ERR_FAIL_COND(!grid_map); + Array cells = grid_map->get_used_cells(); + for (int32_t k = 0; k < cells.size(); k++) { + GLTFNode *new_gltf_node = memnew(GLTFNode); + gltf_node->children.push_back(state->nodes.size()); + state->nodes.push_back(new_gltf_node); + Vector3 cell_location = cells[k]; + int32_t cell = grid_map->get_cell_item( + Vector3(cell_location.x, cell_location.y, cell_location.z)); + EditorSceneImporterMeshNode3D *import_mesh_node = memnew(EditorSceneImporterMeshNode3D); + import_mesh_node->set_mesh(grid_map->get_mesh_library()->get_item_mesh(cell)); + Transform cell_xform; + cell_xform.basis.set_orthogonal_index( + grid_map->get_cell_item_orientation( + Vector3(cell_location.x, cell_location.y, cell_location.z))); + cell_xform.basis.scale(Vector3(grid_map->get_cell_scale(), + grid_map->get_cell_scale(), + grid_map->get_cell_scale())); + cell_xform.set_origin(grid_map->map_to_world( + Vector3(cell_location.x, cell_location.y, cell_location.z))); + Ref<GLTFMesh> gltf_mesh; + gltf_mesh.instance(); + gltf_mesh = import_mesh_node; + new_gltf_node->mesh = state->meshes.size(); + state->meshes.push_back(gltf_mesh); + new_gltf_node->xform = cell_xform * grid_map->get_transform(); + new_gltf_node->set_name(_gen_unique_name(state, grid_map->get_mesh_library()->get_item_name(cell))); + } +} + +void GLTFDocument::_convert_mult_mesh_instance_to_gltf(Node *p_scene_parent, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref<GLTFNode> gltf_node, Ref<GLTFState> state, Node *p_root_node) { + MultiMeshInstance3D *multi_mesh_instance = Object::cast_to<MultiMeshInstance3D>(p_scene_parent); + ERR_FAIL_COND(!multi_mesh_instance); + Ref<MultiMesh> multi_mesh = multi_mesh_instance->get_multimesh(); + if (multi_mesh.is_valid()) { + for (int32_t instance_i = 0; instance_i < multi_mesh->get_instance_count(); + instance_i++) { + GLTFNode *new_gltf_node = memnew(GLTFNode); + Transform transform; + if (multi_mesh->get_transform_format() == MultiMesh::TRANSFORM_2D) { + Transform2D xform_2d = multi_mesh->get_instance_transform_2d(instance_i); + transform.origin = + Vector3(xform_2d.get_origin().x, 0, xform_2d.get_origin().y); + real_t rotation = xform_2d.get_rotation(); + Quat quat; + quat.set_axis_angle(Vector3(0, 1, 0), rotation); + Size2 scale = xform_2d.get_scale(); + transform.basis.set_quat_scale(quat, + Vector3(scale.x, 0, scale.y)); + transform = + multi_mesh_instance->get_transform() * transform; + } else if (multi_mesh->get_transform_format() == MultiMesh::TRANSFORM_3D) { + transform = multi_mesh_instance->get_transform() * + multi_mesh->get_instance_transform(instance_i); + } + Ref<ArrayMesh> mm = multi_mesh->get_mesh(); + if (mm.is_valid()) { + Ref<EditorSceneImporterMesh> mesh; + mesh.instance(); + for (int32_t surface_i = 0; surface_i < mm->get_surface_count(); surface_i++) { + Array surface = mm->surface_get_arrays(surface_i); + mesh->add_surface(mm->surface_get_primitive_type(surface_i), surface, Array(), Dictionary(), + mm->surface_get_material(surface_i), mm->get_name()); + } + Ref<GLTFMesh> gltf_mesh; + gltf_mesh.instance(); + gltf_mesh->set_name(multi_mesh->get_name()); + gltf_mesh->set_mesh(mesh); + new_gltf_node->mesh = state->meshes.size(); + state->meshes.push_back(gltf_mesh); + } + new_gltf_node->xform = transform; + new_gltf_node->set_name(_gen_unique_name(state, multi_mesh_instance->get_name())); + gltf_node->children.push_back(state->nodes.size()); + state->nodes.push_back(new_gltf_node); + } + } +} + +void GLTFDocument::_convert_skeleton_to_gltf(Node *p_scene_parent, Ref<GLTFState> state, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref<GLTFNode> gltf_node, Node *p_root_node) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_scene_parent); + if (skeleton) { + // Remove placeholder skeleton3d node by not creating the gltf node + // Skins are per mesh + for (int node_i = 0; node_i < skeleton->get_child_count(); node_i++) { + _convert_scene_node(state, skeleton->get_child(node_i), p_root_node, p_parent_node_index, p_root_node_index); + } + } +} + +void GLTFDocument::_convert_bone_attachment_to_gltf(Node *p_scene_parent, Ref<GLTFState> state, Ref<GLTFNode> gltf_node, bool &retflag) { + retflag = true; + BoneAttachment3D *bone_attachment = Object::cast_to<BoneAttachment3D>(p_scene_parent); + if (bone_attachment) { + Node *node = bone_attachment->get_parent(); + while (node) { + Skeleton3D *bone_attachment_skeleton = Object::cast_to<Skeleton3D>(node); + if (bone_attachment_skeleton) { + for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) { + if (state->skeletons[skeleton_i]->godot_skeleton != bone_attachment_skeleton) { + continue; + } + state->skeletons.write[skeleton_i]->bone_attachments.push_back(bone_attachment); + break; + } + break; + } + node = node->get_parent(); + } + gltf_node.unref(); + return; + } + retflag = false; +} + +void GLTFDocument::_convert_mesh_to_gltf(Node *p_scene_parent, Ref<GLTFState> state, Node3D *spatial, Ref<GLTFNode> gltf_node) { + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_scene_parent); + if (mi) { + GLTFMeshIndex gltf_mesh_index = _convert_mesh_instance(state, mi); + if (gltf_mesh_index != -1) { + gltf_node->mesh = gltf_mesh_index; + } + } +} + +void GLTFDocument::_generate_scene_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) { + Ref<GLTFNode> gltf_node = state->nodes[node_index]; + + Node3D *current_node = nullptr; + + // Is our parent a skeleton + Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(scene_parent); + + if (gltf_node->skeleton >= 0) { + Skeleton3D *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) { + BoneAttachment3D *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, "BoneAttachment3D")); + + // 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 if (gltf_node->light >= 0) { + current_node = _generate_light(state, scene_parent, node_index); + } + + if (!current_node) { + current_node = _generate_spatial(state, scene_parent, node_index); + } + + scene_parent->add_child(current_node); + if (current_node != scene_root) { + current_node->set_owner(scene_root); + } + current_node->set_transform(gltf_node->xform); + current_node->set_name(gltf_node->get_name()); + } + + state->scene_nodes.insert(node_index, current_node); + + for (int i = 0; i < gltf_node->children.size(); ++i) { + _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]); + } +} + +template <class T> +struct EditorSceneImporterGLTFInterpolate { + T lerp(const T &a, const T &b, float c) const { + return a + (b - a) * c; + } + + T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) { + const float t2 = t * t; + const float t3 = t2 * t; + + return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + } + + T bezier(T start, T control_1, T control_2, T end, float t) { + /* Formula from Wikipedia article on Bezier curves. */ + const real_t omt = (1.0 - t); + const real_t omt2 = omt * omt; + const real_t omt3 = omt2 * omt; + const real_t t2 = t * t; + const real_t t3 = t2 * t; + + return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; + } +}; + +// thank you for existing, partial specialization +template <> +struct EditorSceneImporterGLTFInterpolate<Quat> { + Quat lerp(const Quat &a, const Quat &b, const float c) const { + ERR_FAIL_COND_V_MSG(!a.is_normalized(), Quat(), "The quaternion \"a\" must be normalized."); + ERR_FAIL_COND_V_MSG(!b.is_normalized(), Quat(), "The quaternion \"b\" must be normalized."); + + return a.slerp(b, c).normalized(); + } + + Quat catmull_rom(const Quat &p0, const Quat &p1, const Quat &p2, const Quat &p3, const float c) { + ERR_FAIL_COND_V_MSG(!p1.is_normalized(), Quat(), "The quaternion \"p1\" must be normalized."); + ERR_FAIL_COND_V_MSG(!p2.is_normalized(), Quat(), "The quaternion \"p2\" must be normalized."); + + return p1.slerp(p2, c).normalized(); + } + + Quat bezier(const Quat start, const Quat control_1, const Quat control_2, const Quat end, const float t) { + ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quat(), "The start quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quat(), "The end quaternion must be normalized."); + + return start.slerp(end, t).normalized(); + } +}; + +template <class T> +T GLTFDocument::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { + //could use binary search, worth it? + int idx = -1; + for (int i = 0; i < p_times.size(); i++) { + if (p_times[i] > p_time) + break; + idx++; + } + + EditorSceneImporterGLTFInterpolate<T> interp; + + switch (p_interp) { + case GLTFAnimation::INTERP_LINEAR: { + if (idx == -1) { + return p_values[0]; + } else if (idx >= p_times.size() - 1) { + return p_values[p_times.size() - 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + return interp.lerp(p_values[idx], p_values[idx + 1], c); + } break; + case GLTFAnimation::INTERP_STEP: { + if (idx == -1) { + return p_values[0]; + } else if (idx >= p_times.size() - 1) { + return p_values[p_times.size() - 1]; + } + + return p_values[idx]; + } break; + case GLTFAnimation::INTERP_CATMULLROMSPLINE: { + if (idx == -1) { + return p_values[1]; + } else if (idx >= p_times.size() - 1) { + return p_values[1 + p_times.size() - 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + return interp.catmull_rom(p_values[idx - 1], p_values[idx], p_values[idx + 1], p_values[idx + 3], c); + } break; + case GLTFAnimation::INTERP_CUBIC_SPLINE: { + if (idx == -1) { + return p_values[1]; + } else if (idx >= p_times.size() - 1) { + return p_values[(p_times.size() - 1) * 3 + 1]; + } + + const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + + const T from = p_values[idx * 3 + 1]; + const T c1 = from + p_values[idx * 3 + 2]; + const T to = p_values[idx * 3 + 4]; + const T c2 = to + p_values[idx * 3 + 3]; + + return interp.bezier(from, c1, c2, to, c); + } break; + } + + ERR_FAIL_V(p_values[0]); +} + +void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) { + Ref<GLTFAnimation> anim = state->animations[index]; + + String name = anim->get_name(); + if (name.is_empty()) { + // No node represent these, and they are not in the hierarchy, so just make a unique name + name = _gen_unique_name(state, "Animation"); + } + + Ref<Animation> animation; + animation.instance(); + animation->set_name(name); + + if (anim->get_loop()) { + animation->set_loop(true); + } + + float length = 0; + + for (Map<int, GLTFAnimation::Track>::Element *track_i = anim->get_tracks().front(); track_i; track_i = track_i->next()) { + const GLTFAnimation::Track &track = track_i->get(); + //need to find the path + NodePath node_path; + + GLTFNodeIndex node_index = track_i->key(); + if (state->nodes[node_index]->fake_joint_parent >= 0) { + // Should be same as parent + node_index = state->nodes[node_index]->fake_joint_parent; + } + + const Ref<GLTFNode> gltf_node = state->nodes[track_i->key()]; + + if (gltf_node->skeleton >= 0) { + const Skeleton3D *sk = Object::cast_to<Skeleton3D>(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 = gltf_node->get_name(); + node_path = path + ":" + bone; + } else { + Node *root = ap->get_parent(); + Node *godot_node = state->scene_nodes.find(node_index)->get(); + node_path = root->get_path_to(godot_node); + } + + for (int i = 0; i < track.rotation_track.times.size(); i++) { + length = MAX(length, track.rotation_track.times[i]); + } + for (int i = 0; i < track.translation_track.times.size(); i++) { + length = MAX(length, track.translation_track.times[i]); + } + for (int i = 0; i < track.scale_track.times.size(); i++) { + length = MAX(length, track.scale_track.times[i]); + } + + for (int i = 0; i < track.weight_tracks.size(); i++) { + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + length = MAX(length, track.weight_tracks[i].times[j]); + } + } + + if (track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) { + //make transform track + int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_TRANSFORM); + animation->track_set_path(track_idx, node_path); + //first determine animation length + + const 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[track_i->key()]->rotation.normalized(); + } + + if (!track.translation_track.values.size()) { + base_pos = state->nodes[track_i->key()]->translation; + } + + if (!track.scale_track.values.size()) { + base_scale = state->nodes[track_i->key()]->scale; + } + + bool last = false; + while (true) { + Vector3 pos = base_pos; + Quat rot = base_rot; + Vector3 scale = base_scale; + + if (track.translation_track.times.size()) { + pos = _interpolate_track<Vector3>(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation); + } + + if (track.rotation_track.times.size()) { + rot = _interpolate_track<Quat>(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); + } + + if (track.scale_track.times.size()) { + scale = _interpolate_track<Vector3>(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); + } + + if (gltf_node->skeleton >= 0) { + Transform xform; + xform.basis.set_quat_scale(rot, scale); + xform.origin = pos; + + const Skeleton3D *skeleton = state->skeletons[gltf_node->skeleton]->godot_skeleton; + const int bone_idx = skeleton->find_bone(gltf_node->get_name()); + xform = skeleton->get_bone_rest(bone_idx).affine_inverse() * xform; + + rot = xform.basis.get_rotation_quat(); + rot.normalize(); + scale = xform.basis.get_scale(); + pos = xform.origin; + } + + animation->transform_track_insert_key(track_idx, time, pos, rot, scale); + + if (last) { + break; + } + time += increment; + if (time >= length) { + last = true; + time = length; + } + } + } + + for (int i = 0; i < track.weight_tracks.size(); i++) { + ERR_CONTINUE(gltf_node->mesh < 0 || gltf_node->mesh >= state->meshes.size()); + Ref<GLTFMesh> mesh = state->meshes[gltf_node->mesh]; + ERR_CONTINUE(mesh.is_null()); + ERR_CONTINUE(mesh->get_mesh().is_null()); + ERR_CONTINUE(mesh->get_mesh()->get_mesh().is_null()); + const String prop = "blend_shapes/" + mesh->get_mesh()->get_blend_shape_name(i); + + const String blend_path = String(node_path) + ":" + prop; + + const int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_VALUE); + animation->track_set_path(track_idx, blend_path); + + // Only LINEAR and STEP (NEAREST) can be supported out of the box by Godot's Animation, + // the other modes have to be baked. + GLTFAnimation::Interpolation gltf_interp = track.weight_tracks[i].interpolation; + if (gltf_interp == GLTFAnimation::INTERP_LINEAR || gltf_interp == GLTFAnimation::INTERP_STEP) { + animation->track_set_interpolation_type(track_idx, gltf_interp == GLTFAnimation::INTERP_STEP ? Animation::INTERPOLATION_NEAREST : Animation::INTERPOLATION_LINEAR); + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + const float t = track.weight_tracks[i].times[j]; + const float attribs = track.weight_tracks[i].values[j]; + animation->track_insert_key(track_idx, t, attribs); + } + } else { + // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. + const float increment = 1.0 / float(bake_fps); + float time = 0.0; + bool last = false; + while (true) { + _interpolate_track<float>(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); + if (last) { + break; + } + time += increment; + if (time >= length) { + last = true; + time = length; + } + } + } + } + } + + animation->set_length(length); + + ap->add_animation(name, animation); +} + +void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) { + for (GLTFNodeIndex mi_node_i = 0; mi_node_i < state->nodes.size(); ++mi_node_i) { + Ref<GLTFNode> node = state->nodes[mi_node_i]; + + if (node->mesh < 0) { + continue; + } + Array json_skins; + if (state->json.has("skins")) { + json_skins = state->json["skins"]; + } + Map<GLTFNodeIndex, Node *>::Element *mi_element = state->scene_nodes.find(mi_node_i); + if (!mi_element) { + continue; + } + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(mi_element->get()); + ERR_CONTINUE(!mi); + Transform mi_xform = mi->get_transform(); + node->scale = mi_xform.basis.get_scale(); + node->rotation = mi_xform.basis.get_rotation_quat(); + node->translation = mi_xform.origin; + + Dictionary json_skin; + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(mi->get_node(mi->get_skeleton_path())); + if (!skeleton) { + continue; + } + if (!skeleton->get_bone_count()) { + continue; + } + Ref<Skin> skin = mi->get_skin(); + if (skin.is_null()) { + skin = skeleton->register_skin(nullptr)->get_skin(); + } + Ref<GLTFSkin> gltf_skin; + gltf_skin.instance(); + Array json_joints; + GLTFSkeletonIndex skeleton_gltf_i = -1; + + NodePath skeleton_path = mi->get_skeleton_path(); + bool is_unique = true; + for (int32_t skin_i = 0; skin_i < state->skins.size(); skin_i++) { + Ref<GLTFSkin> prev_gltf_skin = state->skins.write[skin_i]; + if (gltf_skin.is_null()) { + continue; + } + GLTFSkeletonIndex prev_skeleton = prev_gltf_skin->get_skeleton(); + if (prev_skeleton == -1 || prev_skeleton >= state->skeletons.size()) { + continue; + } + if (prev_gltf_skin->get_godot_skin() == skin && state->skeletons[prev_skeleton]->godot_skeleton == skeleton) { + node->skin = skin_i; + node->skeleton = prev_skeleton; + is_unique = false; + break; + } + } + if (!is_unique) { + continue; + } + GLTFSkeletonIndex skeleton_i = _convert_skeleton(state, skeleton); + skeleton_gltf_i = skeleton_i; + ERR_CONTINUE(skeleton_gltf_i == -1); + gltf_skin->skeleton = skeleton_gltf_i; + Ref<GLTFSkeleton> gltf_skeleton = state->skeletons.write[skeleton_gltf_i]; + for (int32_t bind_i = 0; bind_i < skin->get_bind_count(); bind_i++) { + String godot_bone_name = skin->get_bind_name(bind_i); + if (godot_bone_name.is_empty()) { + int32_t bone = skin->get_bind_bone(bind_i); + godot_bone_name = skeleton->get_bone_name(bone); + } + if (skeleton->find_bone(godot_bone_name) == -1) { + godot_bone_name = skeleton->get_bone_name(0); + } + BoneId bone_index = skeleton->find_bone(godot_bone_name); + ERR_CONTINUE(bone_index == -1); + Ref<GLTFNode> joint_node; + joint_node.instance(); + String gltf_bone_name = _gen_unique_bone_name(state, skeleton_gltf_i, godot_bone_name); + joint_node->set_name(gltf_bone_name); + + Transform bone_rest_xform = skeleton->get_bone_rest(bone_index); + joint_node->scale = bone_rest_xform.basis.get_scale(); + joint_node->rotation = bone_rest_xform.basis.get_rotation_quat(); + joint_node->translation = bone_rest_xform.origin; + joint_node->joint = true; + + int32_t joint_node_i = state->nodes.size(); + state->nodes.push_back(joint_node); + gltf_skeleton->godot_bone_node.insert(bone_index, joint_node_i); + int32_t joint_index = gltf_skin->joints.size(); + gltf_skin->joint_i_to_bone_i.insert(joint_index, bone_index); + gltf_skin->joints.push_back(joint_node_i); + gltf_skin->joints_original.push_back(joint_node_i); + gltf_skin->inverse_binds.push_back(skin->get_bind_pose(bind_i)); + json_joints.push_back(joint_node_i); + for (Map<GLTFNodeIndex, Node *>::Element *skin_scene_node_i = state->scene_nodes.front(); skin_scene_node_i; skin_scene_node_i = skin_scene_node_i->next()) { + if (skin_scene_node_i->get() == skeleton) { + gltf_skin->skin_root = skin_scene_node_i->key(); + json_skin["skeleton"] = skin_scene_node_i->key(); + } + } + gltf_skin->godot_skin = skin; + gltf_skin->set_name(_gen_unique_name(state, skin->get_name())); + } + for (int32_t bind_i = 0; bind_i < skin->get_bind_count(); bind_i++) { + String bone_name = skeleton->get_bone_name(bind_i); + String godot_bone_name = skin->get_bind_name(bind_i); + int32_t bone = -1; + if (skin->get_bind_bone(bind_i) != -1) { + bone = skin->get_bind_bone(bind_i); + godot_bone_name = skeleton->get_bone_name(bone); + } + bone = skeleton->find_bone(godot_bone_name); + if (bone == -1) { + continue; + } + BoneId bone_parent = skeleton->get_bone_parent(bone); + GLTFNodeIndex joint_node_i = gltf_skeleton->godot_bone_node[bone]; + ERR_CONTINUE(joint_node_i >= state->nodes.size()); + if (bone_parent != -1) { + GLTFNodeIndex parent_joint_gltf_node = gltf_skin->joints[bone_parent]; + Ref<GLTFNode> parent_joint_node = state->nodes.write[parent_joint_gltf_node]; + parent_joint_node->children.push_back(joint_node_i); + } else { + Node *node_parent = skeleton->get_parent(); + ERR_CONTINUE(!node_parent); + for (Map<GLTFNodeIndex, Node *>::Element *E = state->scene_nodes.front(); E; E = E->next()) { + if (E->get() == node_parent) { + GLTFNodeIndex gltf_node_i = E->key(); + Ref<GLTFNode> gltf_node = state->nodes.write[gltf_node_i]; + gltf_node->children.push_back(joint_node_i); + break; + } + } + } + } + _expand_skin(state, gltf_skin); + node->skin = state->skins.size(); + state->skins.push_back(gltf_skin); + + json_skin["inverseBindMatrices"] = _encode_accessor_as_xform(state, gltf_skin->inverse_binds, false); + json_skin["joints"] = json_joints; + json_skin["name"] = gltf_skin->get_name(); + json_skins.push_back(json_skin); + state->json["skins"] = json_skins; + } +} + +float GLTFDocument::solve_metallic(float p_dielectric_specular, float diffuse, float specular, float p_one_minus_specular_strength) { + if (specular <= p_dielectric_specular) { + return 0.0f; + } + + const float a = p_dielectric_specular; + const float b = diffuse * p_one_minus_specular_strength / (1.0f - p_dielectric_specular) + specular - 2.0f * p_dielectric_specular; + const float c = p_dielectric_specular - specular; + const float D = b * b - 4.0f * a * c; + return CLAMP((-b + Math::sqrt(D)) / (2.0f * a), 0.0f, 1.0f); +} + +float GLTFDocument::get_perceived_brightness(const Color p_color) { + const Color coeff = Color(R_BRIGHTNESS_COEFF, G_BRIGHTNESS_COEFF, B_BRIGHTNESS_COEFF); + const Color value = coeff * (p_color * p_color); + + const float r = value.r; + const float g = value.g; + const float b = value.b; + + return Math::sqrt(r + g + b); +} + +float GLTFDocument::get_max_component(const Color &p_color) { + const float r = p_color.r; + const float g = p_color.g; + const float b = p_color.b; + + return MAX(MAX(r, g), b); +} + +void GLTFDocument::_process_mesh_instances(Ref<GLTFState> state, Node *scene_root) { + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); ++node_i) { + Ref<GLTFNode> node = state->nodes[node_i]; + + if (node->skin >= 0 && node->mesh >= 0) { + const GLTFSkinIndex skin_i = node->skin; + + Map<GLTFNodeIndex, Node *>::Element *mi_element = state->scene_nodes.find(node_i); + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(mi_element->get()); + ERR_FAIL_COND(mi == nullptr); + + const GLTFSkeletonIndex skel_i = state->skins.write[node->skin]->skeleton; + Ref<GLTFSkeleton> gltf_skeleton = state->skeletons.write[skel_i]; + Skeleton3D *skeleton = gltf_skeleton->godot_skeleton; + ERR_FAIL_COND(skeleton == nullptr); + + mi->get_parent()->remove_child(mi); + skeleton->add_child(mi); + mi->set_owner(skeleton->get_owner()); + + mi->set_skin(state->skins.write[skin_i]->godot_skin); + mi->set_skeleton_path(mi->get_path_to(skeleton)); + mi->set_transform(Transform()); + } + } +} + +GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state, GLTFAnimation::Track p_track, Ref<Animation> p_animation, Transform p_bone_rest, int32_t p_track_i, GLTFNodeIndex p_node_i) { + Animation::InterpolationType interpolation = p_animation->track_get_interpolation_type(p_track_i); + + GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) { + gltf_interpolation = GLTFAnimation::INTERP_STEP; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) { + gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE; + } + Animation::TrackType track_type = p_animation->track_get_type(p_track_i); + int32_t key_count = p_animation->track_get_key_count(p_track_i); + Vector<float> times; + times.resize(key_count); + String path = p_animation->track_get_path(p_track_i); + for (int32_t key_i = 0; key_i < key_count; key_i++) { + times.write[key_i] = p_animation->track_get_key_time(p_track_i, key_i); + } + const float BAKE_FPS = 30.0f; + if (track_type == Animation::TYPE_TRANSFORM) { + p_track.translation_track.times = times; + p_track.translation_track.interpolation = gltf_interpolation; + p_track.rotation_track.times = times; + p_track.rotation_track.interpolation = gltf_interpolation; + p_track.scale_track.times = times; + p_track.scale_track.interpolation = gltf_interpolation; + + p_track.scale_track.values.resize(key_count); + p_track.scale_track.interpolation = gltf_interpolation; + p_track.translation_track.values.resize(key_count); + p_track.translation_track.interpolation = gltf_interpolation; + p_track.rotation_track.values.resize(key_count); + p_track.rotation_track.interpolation = gltf_interpolation; + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 translation; + Quat rotation; + Vector3 scale; + Error err = p_animation->transform_track_get_key(p_track_i, key_i, &translation, &rotation, &scale); + ERR_CONTINUE(err != OK); + Transform xform; + xform.basis.set_quat_scale(rotation, scale); + xform.origin = translation; + xform = p_bone_rest * xform; + p_track.translation_track.values.write[key_i] = xform.get_origin(); + p_track.rotation_track.values.write[key_i] = xform.basis.get_rotation_quat(); + p_track.scale_track.values.write[key_i] = xform.basis.get_scale(); + } + } else if (path.find(":transform") != -1) { + p_track.translation_track.times = times; + p_track.translation_track.interpolation = gltf_interpolation; + p_track.rotation_track.times = times; + p_track.rotation_track.interpolation = gltf_interpolation; + p_track.scale_track.times = times; + p_track.scale_track.interpolation = gltf_interpolation; + + p_track.scale_track.values.resize(key_count); + p_track.scale_track.interpolation = gltf_interpolation; + p_track.translation_track.values.resize(key_count); + p_track.translation_track.interpolation = gltf_interpolation; + p_track.rotation_track.values.resize(key_count); + p_track.rotation_track.interpolation = gltf_interpolation; + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Transform xform = p_animation->track_get_key_value(p_track_i, key_i); + p_track.translation_track.values.write[key_i] = xform.get_origin(); + p_track.rotation_track.values.write[key_i] = xform.basis.get_rotation_quat(); + p_track.scale_track.values.write[key_i] = xform.basis.get_scale(); + } + } else if (track_type == Animation::TYPE_VALUE) { + if (path.find("/rotation_quat") != -1) { + p_track.rotation_track.times = times; + p_track.rotation_track.interpolation = gltf_interpolation; + + p_track.rotation_track.values.resize(key_count); + p_track.rotation_track.interpolation = gltf_interpolation; + + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Quat rotation_track = p_animation->track_get_key_value(p_track_i, key_i); + p_track.rotation_track.values.write[key_i] = rotation_track; + } + } else if (path.find(":translation") != -1) { + p_track.translation_track.times = times; + p_track.translation_track.interpolation = gltf_interpolation; + + p_track.translation_track.values.resize(key_count); + p_track.translation_track.interpolation = gltf_interpolation; + + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 translation = p_animation->track_get_key_value(p_track_i, key_i); + p_track.translation_track.values.write[key_i] = translation; + } + } else if (path.find(":rotation_degrees") != -1) { + p_track.rotation_track.times = times; + p_track.rotation_track.interpolation = gltf_interpolation; + + p_track.rotation_track.values.resize(key_count); + p_track.rotation_track.interpolation = gltf_interpolation; + + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Quat rotation; + Vector3 rotation_degrees = p_animation->track_get_key_value(p_track_i, key_i); + Vector3 rotation_radian; + rotation_radian.x = Math::deg2rad(rotation_degrees.x); + rotation_radian.y = Math::deg2rad(rotation_degrees.y); + rotation_radian.z = Math::deg2rad(rotation_degrees.z); + rotation.set_euler(rotation_radian); + p_track.rotation_track.values.write[key_i] = rotation; + } + } else if (path.find(":scale") != -1) { + p_track.scale_track.times = times; + p_track.scale_track.interpolation = gltf_interpolation; + + p_track.scale_track.values.resize(key_count); + p_track.scale_track.interpolation = gltf_interpolation; + + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i); + p_track.scale_track.values.write[key_i] = scale_track; + } + } + } else if (track_type == Animation::TYPE_BEZIER) { + if (path.find("/scale") != -1) { + const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; + if (!p_track.scale_track.times.size()) { + Vector<float> new_times; + new_times.resize(keys); + for (int32_t key_i = 0; key_i < keys; key_i++) { + new_times.write[key_i] = key_i / BAKE_FPS; + } + p_track.scale_track.times = new_times; + p_track.scale_track.interpolation = gltf_interpolation; + + p_track.scale_track.values.resize(keys); + + for (int32_t key_i = 0; key_i < keys; key_i++) { + p_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f); + } + p_track.scale_track.interpolation = gltf_interpolation; + } + + for (int32_t key_i = 0; key_i < keys; key_i++) { + Vector3 bezier_track = p_track.scale_track.values[key_i]; + if (path.find("/scale:x") != -1) { + bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.x = p_bone_rest.affine_inverse().basis.get_scale().x * bezier_track.x; + } else if (path.find("/scale:y") != -1) { + bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.y = p_bone_rest.affine_inverse().basis.get_scale().y * bezier_track.y; + } else if (path.find("/scale:z") != -1) { + bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.z = p_bone_rest.affine_inverse().basis.get_scale().z * bezier_track.z; + } + p_track.scale_track.values.write[key_i] = bezier_track; + } + } else if (path.find("/translation") != -1) { + const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; + if (!p_track.translation_track.times.size()) { + Vector<float> new_times; + new_times.resize(keys); + for (int32_t key_i = 0; key_i < keys; key_i++) { + new_times.write[key_i] = key_i / BAKE_FPS; + } + p_track.translation_track.times = new_times; + p_track.translation_track.interpolation = gltf_interpolation; + + p_track.translation_track.values.resize(keys); + p_track.translation_track.interpolation = gltf_interpolation; + } + + for (int32_t key_i = 0; key_i < keys; key_i++) { + Vector3 bezier_track = p_track.translation_track.values[key_i]; + if (path.find("/translation:x") != -1) { + bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.x = p_bone_rest.affine_inverse().origin.x * bezier_track.x; + } else if (path.find("/translation:y") != -1) { + bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.y = p_bone_rest.affine_inverse().origin.y * bezier_track.y; + } else if (path.find("/translation:z") != -1) { + bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.z = p_bone_rest.affine_inverse().origin.z * bezier_track.z; + } + p_track.translation_track.values.write[key_i] = bezier_track; + } + } + } + + return p_track; +} + +void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, String p_animation_track_name) { + Ref<Animation> animation = ap->get_animation(p_animation_track_name); + Ref<GLTFAnimation> gltf_animation; + gltf_animation.instance(); + gltf_animation->set_name(_gen_unique_name(state, p_animation_track_name)); + + for (int32_t track_i = 0; track_i < animation->get_track_count(); track_i++) { + if (!animation->track_is_enabled(track_i)) { + continue; + } + String orig_track_path = animation->track_get_path(track_i); + if (String(orig_track_path).find(":translation") != -1) { + const Vector<String> node_suffix = String(orig_track_path).split(":translation"); + const NodePath path = node_suffix[0]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map<GLTFNodeIndex, Node *>::Element *translation_scene_node_i = state->scene_nodes.front(); translation_scene_node_i; translation_scene_node_i = translation_scene_node_i->next()) { + if (translation_scene_node_i->get() == node) { + GLTFNodeIndex node_index = translation_scene_node_i->key(); + Map<int, GLTFAnimation::Track>::Element *translation_track_i = gltf_animation->get_tracks().find(node_index); + GLTFAnimation::Track track; + if (translation_track_i) { + track = translation_track_i->get(); + } + track = _convert_animation_track(state, track, animation, Transform(), track_i, node_index); + gltf_animation->get_tracks().insert(node_index, track); + } + } + } else if (String(orig_track_path).find(":rotation_degrees") != -1) { + const Vector<String> node_suffix = String(orig_track_path).split(":rotation_degrees"); + const NodePath path = node_suffix[0]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map<GLTFNodeIndex, Node *>::Element *rotation_degree_scene_node_i = state->scene_nodes.front(); rotation_degree_scene_node_i; rotation_degree_scene_node_i = rotation_degree_scene_node_i->next()) { + if (rotation_degree_scene_node_i->get() == node) { + GLTFNodeIndex node_index = rotation_degree_scene_node_i->key(); + Map<int, GLTFAnimation::Track>::Element *rotation_degree_track_i = gltf_animation->get_tracks().find(node_index); + GLTFAnimation::Track track; + if (rotation_degree_track_i) { + track = rotation_degree_track_i->get(); + } + track = _convert_animation_track(state, track, animation, Transform(), track_i, node_index); + gltf_animation->get_tracks().insert(node_index, track); + } + } + } else if (String(orig_track_path).find(":scale") != -1) { + const Vector<String> node_suffix = String(orig_track_path).split(":scale"); + const NodePath path = node_suffix[0]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map<GLTFNodeIndex, Node *>::Element *scale_scene_node_i = state->scene_nodes.front(); scale_scene_node_i; scale_scene_node_i = scale_scene_node_i->next()) { + if (scale_scene_node_i->get() == node) { + GLTFNodeIndex node_index = scale_scene_node_i->key(); + Map<int, GLTFAnimation::Track>::Element *scale_track_i = gltf_animation->get_tracks().find(node_index); + GLTFAnimation::Track track; + if (scale_track_i) { + track = scale_track_i->get(); + } + track = _convert_animation_track(state, track, animation, Transform(), track_i, node_index); + gltf_animation->get_tracks().insert(node_index, track); + } + } + } else if (String(orig_track_path).find(":transform") != -1) { + const Vector<String> node_suffix = String(orig_track_path).split(":transform"); + const NodePath path = node_suffix[0]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map<GLTFNodeIndex, Node *>::Element *transform_track_i = state->scene_nodes.front(); transform_track_i; transform_track_i = transform_track_i->next()) { + if (transform_track_i->get() == node) { + GLTFAnimation::Track track; + track = _convert_animation_track(state, track, animation, Transform(), track_i, transform_track_i->key()); + gltf_animation->get_tracks().insert(transform_track_i->key(), track); + } + } + } else if (String(orig_track_path).find(":blend_shapes/") != -1) { + const Vector<String> node_suffix = String(orig_track_path).split(":blend_shapes/"); + const NodePath path = node_suffix[0]; + const String suffix = node_suffix[1]; + const Node *node = ap->get_parent()->get_node_or_null(path); + for (Map<GLTFNodeIndex, Node *>::Element *transform_track_i = state->scene_nodes.front(); transform_track_i; transform_track_i = transform_track_i->next()) { + if (transform_track_i->get() == node) { + const MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(node); + if (!mi) { + continue; + } + Ref<ArrayMesh> array_mesh = mi->get_mesh(); + if (array_mesh.is_null()) { + continue; + } + if (node_suffix.size() != 2) { + continue; + } + GLTFNodeIndex mesh_index = -1; + for (GLTFNodeIndex node_i = 0; node_i < state->scene_nodes.size(); node_i++) { + if (state->scene_nodes[node_i] == node) { + mesh_index = node_i; + break; + } + } + ERR_CONTINUE(mesh_index == -1); + Ref<Mesh> mesh = mi->get_mesh(); + ERR_CONTINUE(mesh.is_null()); + for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) { + if (mesh->get_blend_shape_name(shape_i) != suffix) { + continue; + } + GLTFAnimation::Track track; + Map<int, GLTFAnimation::Track>::Element *blend_shape_track_i = gltf_animation->get_tracks().find(mesh_index); + if (blend_shape_track_i) { + track = blend_shape_track_i->get(); + } + Animation::InterpolationType interpolation = animation->track_get_interpolation_type(track_i); + + GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) { + gltf_interpolation = GLTFAnimation::INTERP_STEP; + } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) { + gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE; + } + Animation::TrackType track_type = animation->track_get_type(track_i); + if (track_type == Animation::TYPE_VALUE) { + int32_t key_count = animation->track_get_key_count(track_i); + GLTFAnimation::Channel<float> weight; + weight.interpolation = gltf_interpolation; + weight.times.resize(key_count); + for (int32_t time_i = 0; time_i < key_count; time_i++) { + weight.times.write[time_i] = animation->track_get_key_time(track_i, time_i); + } + weight.values.resize(key_count); + for (int32_t value_i = 0; value_i < key_count; value_i++) { + weight.values.write[value_i] = animation->track_get_key_value(track_i, value_i); + } + track.weight_tracks.push_back(weight); + } + gltf_animation->get_tracks()[mesh_index] = track; + } + } + } + + } else if (String(orig_track_path).find(":") != -1) { + //Process skeleton + const Vector<String> node_suffix = String(orig_track_path).split(":"); + const String node = node_suffix[0]; + const NodePath node_path = node; + const String suffix = node_suffix[1]; + Node *godot_node = ap->get_parent()->get_node_or_null(node_path); + Skeleton3D *skeleton = nullptr; + GLTFSkeletonIndex skeleton_gltf_i = -1; + for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) { + if (state->skeletons[skeleton_i]->godot_skeleton == cast_to<Skeleton3D>(godot_node)) { + skeleton = state->skeletons[skeleton_i]->godot_skeleton; + skeleton_gltf_i = skeleton_i; + ERR_CONTINUE(!skeleton); + Ref<GLTFSkeleton> skeleton_gltf = state->skeletons[skeleton_gltf_i]; + int32_t bone = skeleton->find_bone(suffix); + ERR_CONTINUE(bone == -1); + Transform xform = skeleton->get_bone_rest(bone); + if (!skeleton_gltf->godot_bone_node.has(bone)) { + continue; + } + GLTFNodeIndex node_i = skeleton_gltf->godot_bone_node[bone]; + Map<int, GLTFAnimation::Track>::Element *property_track_i = gltf_animation->get_tracks().find(node_i); + GLTFAnimation::Track track; + if (property_track_i) { + track = property_track_i->get(); + } + track = _convert_animation_track(state, track, animation, xform, track_i, node_i); + gltf_animation->get_tracks()[node_i] = track; + } + } + } else if (String(orig_track_path).find(":") == -1) { + const Node *node = ap->get_parent()->get_node_or_null(orig_track_path); + for (Map<GLTFNodeIndex, Node *>::Element *scene_node_i = state->scene_nodes.front(); scene_node_i; scene_node_i = scene_node_i->next()) { + if (scene_node_i->get() == node) { + GLTFNodeIndex node_index = scene_node_i->key(); + Map<int, GLTFAnimation::Track>::Element *node_track_i = gltf_animation->get_tracks().find(node_index); + GLTFAnimation::Track track; + if (node_track_i) { + track = node_track_i->get(); + } + track = _convert_animation_track(state, track, animation, Transform(), track_i, node_index); + gltf_animation->get_tracks().insert(node_index, track); + break; + } + } + } + } + if (gltf_animation->get_tracks().size()) { + state->animations.push_back(gltf_animation); + } +} + +Error GLTFDocument::parse(Ref<GLTFState> state, String p_path, bool p_read_binary) { + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (!f) { + return err; + } + uint32_t magic = f->get_32(); + if (magic == 0x46546C67) { + //binary file + //text file + err = _parse_glb(p_path, state); + if (err) + return FAILED; + } else { + //text file + err = _parse_json(p_path, state); + if (err) + return FAILED; + } + f->close(); + + ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED); + + Dictionary asset = state->json["asset"]; + + ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED); + + String version = asset["version"]; + + state->major_version = version.get_slice(".", 0).to_int(); + state->minor_version = version.get_slice(".", 1).to_int(); + + /* STEP 0 PARSE SCENE */ + err = _parse_scenes(state); + if (err != OK) + return Error::FAILED; + + /* STEP 1 PARSE NODES */ + err = _parse_nodes(state); + if (err != OK) + return Error::FAILED; + + /* STEP 2 PARSE BUFFERS */ + err = _parse_buffers(state, p_path.get_base_dir()); + if (err != OK) + return Error::FAILED; + + /* STEP 3 PARSE BUFFER VIEWS */ + err = _parse_buffer_views(state); + if (err != OK) + return Error::FAILED; + + /* STEP 4 PARSE ACCESSORS */ + err = _parse_accessors(state); + if (err != OK) + return Error::FAILED; + + /* STEP 5 PARSE IMAGES */ + err = _parse_images(state, p_path.get_base_dir()); + if (err != OK) + return Error::FAILED; + + /* STEP 6 PARSE TEXTURES */ + err = _parse_textures(state); + if (err != OK) + return Error::FAILED; + + /* STEP 7 PARSE TEXTURES */ + err = _parse_materials(state); + if (err != OK) + return Error::FAILED; + + /* STEP 9 PARSE SKINS */ + err = _parse_skins(state); + if (err != OK) + return Error::FAILED; + + /* STEP 10 DETERMINE SKELETONS */ + err = _determine_skeletons(state); + if (err != OK) + return Error::FAILED; + + /* STEP 11 CREATE SKELETONS */ + err = _create_skeletons(state); + if (err != OK) + return Error::FAILED; + + /* STEP 12 CREATE SKINS */ + err = _create_skins(state); + if (err != OK) + return Error::FAILED; + + /* STEP 13 PARSE MESHES (we have enough info now) */ + err = _parse_meshes(state); + if (err != OK) + return Error::FAILED; + + /* STEP 14 PARSE LIGHTS */ + err = _parse_lights(state); + if (err != OK) { + return Error::FAILED; + } + + /* STEP 15 PARSE CAMERAS */ + err = _parse_cameras(state); + if (err != OK) + return Error::FAILED; + + /* STEP 16 PARSE ANIMATIONS */ + err = _parse_animations(state); + if (err != OK) + return Error::FAILED; + + /* STEP 17 ASSIGN SCENE NAMES */ + _assign_scene_names(state); + + return OK; +} + +Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material) { + Dictionary extension; + Ref<BaseMaterial3D> mat = p_material; + if (mat.is_valid()) { + Dictionary texture_transform; + Array offset; + offset.resize(2); + offset[0] = mat->get_uv2_offset().x; + offset[1] = mat->get_uv2_offset().y; + texture_transform["offset"] = offset; + Array scale; + scale.resize(2); + scale[0] = mat->get_uv2_scale().x; + scale[1] = mat->get_uv2_scale().y; + texture_transform["scale"] = scale; + // Godot doesn't support texture rotation + extension["KHR_texture_transform"] = texture_transform; + } + return extension; +} + +Dictionary GLTFDocument::_serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material) { + Dictionary extension; + if (p_material.is_valid()) { + Dictionary texture_transform; + Array offset; + offset.resize(2); + offset[0] = p_material->get_uv1_offset().x; + offset[1] = p_material->get_uv1_offset().y; + texture_transform["offset"] = offset; + Array scale; + scale.resize(2); + scale[0] = p_material->get_uv1_scale().x; + scale[1] = p_material->get_uv1_scale().y; + texture_transform["scale"] = scale; + // Godot doesn't support texture rotation + extension["KHR_texture_transform"] = texture_transform; + } + return extension; +} + +Error GLTFDocument::_serialize_version(Ref<GLTFState> state) { + const String version = "2.0"; + state->major_version = version.get_slice(".", 0).to_int(); + state->minor_version = version.get_slice(".", 1).to_int(); + Dictionary asset; + asset["version"] = version; + + String hash = VERSION_HASH; + asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.length() == 0 ? String("unknown") : hash); + state->json["asset"] = asset; + ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED); + ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED); + return OK; +} + +Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) { + Error err = FAILED; + if (p_path.to_lower().ends_with("glb")) { + err = _encode_buffer_glb(state, p_path); + ERR_FAIL_COND_V(err != OK, err); + FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(!f, FAILED); + + String json = JSON::print(state->json); + + const uint32_t magic = 0x46546C67; // GLTF + const int32_t header_size = 12; + const int32_t chunk_header_size = 8; + + for (int32_t pad_i = 0; pad_i < (chunk_header_size + json.utf8().length()) % 4; pad_i++) { + json += " "; + } + CharString cs = json.utf8(); + const uint32_t text_chunk_length = cs.length(); + + const uint32_t text_chunk_type = 0x4E4F534A; //JSON + int32_t binary_data_length = 0; + if (state->buffers.size()) { + binary_data_length = state->buffers[0].size(); + } + const int32_t binary_chunk_length = binary_data_length; + const int32_t binary_chunk_type = 0x004E4942; //BIN + + f->create(FileAccess::ACCESS_RESOURCES); + f->store_32(magic); + f->store_32(state->major_version); // version + f->store_32(header_size + chunk_header_size + text_chunk_length + chunk_header_size + binary_data_length); // length + f->store_32(text_chunk_length); + f->store_32(text_chunk_type); + f->store_buffer((uint8_t *)&cs[0], cs.length()); + if (binary_chunk_length) { + f->store_32(binary_chunk_length); + f->store_32(binary_chunk_type); + f->store_buffer(state->buffers[0].ptr(), binary_data_length); + } + + f->close(); + } else { + err = _encode_buffer_bins(state, p_path); + ERR_FAIL_COND_V(err != OK, err); + FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(!f, FAILED); + + f->create(FileAccess::ACCESS_RESOURCES); + String json = JSON::print(state->json); + f->store_string(json); + f->close(); + } + return err; +} diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h new file mode 100644 index 0000000000..0f4772cd26 --- /dev/null +++ b/modules/gltf/gltf_document.h @@ -0,0 +1,427 @@ +/*************************************************************************/ +/* gltf_document.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_DOCUMENT_H +#define GLTF_DOCUMENT_H + +#include "editor/import/resource_importer_scene.h" +#include "editor/import/scene_importer_mesh_node_3d.h" +#include "gltf_animation.h" +#include "scene/2d/node_2d.h" +#include "scene/3d/bone_attachment_3d.h" +#include "scene/3d/light_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/material.h" +#include "scene/resources/texture.h" + +class GLTFState; +class GLTFSkin; +class GLTFNode; +class GLTFSpecGloss; +class GLTFSkeleton; + +using GLTFAccessorIndex = int; +using GLTFAnimationIndex = int; +using GLTFBufferIndex = int; +using GLTFBufferViewIndex = int; +using GLTFCameraIndex = int; +using GLTFImageIndex = int; +using GLTFMaterialIndex = int; +using GLTFMeshIndex = int; +using GLTFLightIndex = int; +using GLTFNodeIndex = int; +using GLTFSkeletonIndex = int; +using GLTFSkinIndex = int; +using GLTFTextureIndex = int; + +class GLTFDocument : public Resource { + GDCLASS(GLTFDocument, Resource); + friend class GLTFState; + friend class GLTFSkin; + friend class GLTFSkeleton; + +public: + enum GLTFType { + TYPE_SCALAR, + TYPE_VEC2, + TYPE_VEC3, + TYPE_VEC4, + TYPE_MAT2, + TYPE_MAT3, + TYPE_MAT4, + }; + + enum { + ARRAY_BUFFER = 34962, + ELEMENT_ARRAY_BUFFER = 34963, + + TYPE_BYTE = 5120, + TYPE_UNSIGNED_BYTE = 5121, + TYPE_SHORT = 5122, + TYPE_UNSIGNED_SHORT = 5123, + TYPE_UNSIGNED_INT = 5125, + TYPE_FLOAT = 5126, + + COMPONENT_TYPE_BYTE = 5120, + COMPONENT_TYPE_UNSIGNED_BYTE = 5121, + COMPONENT_TYPE_SHORT = 5122, + COMPONENT_TYPE_UNSIGNED_SHORT = 5123, + COMPONENT_TYPE_INT = 5125, + COMPONENT_TYPE_FLOAT = 5126, + }; + +private: + template <class T> + static Array to_array(const Vector<T> &p_inp) { + Array ret; + for (int i = 0; i < p_inp.size(); i++) { + ret.push_back(p_inp[i]); + } + return ret; + } + + template <class T> + static Array to_array(const Set<T> &p_inp) { + Array ret; + typename Set<T>::Element *elem = p_inp.front(); + while (elem) { + ret.push_back(elem->get()); + elem = elem->next(); + } + return ret; + } + + template <class T> + static void set_from_array(Vector<T> &r_out, const Array &p_inp) { + r_out.clear(); + for (int i = 0; i < p_inp.size(); i++) { + r_out.push_back(p_inp[i]); + } + } + + template <class T> + static void set_from_array(Set<T> &r_out, const Array &p_inp) { + r_out.clear(); + for (int i = 0; i < p_inp.size(); i++) { + r_out.insert(p_inp[i]); + } + } + template <class K, class V> + static Dictionary to_dict(const Map<K, V> &p_inp) { + Dictionary ret; + for (typename Map<K, V>::Element *E = p_inp.front(); E; E = E->next()) { + ret[E->key()] = E->value(); + } + return ret; + } + + template <class K, class V> + static void set_from_dict(Map<K, V> &r_out, const Dictionary &p_inp) { + r_out.clear(); + Array keys = p_inp.keys(); + for (int i = 0; i < keys.size(); i++) { + r_out[keys[i]] = p_inp[keys[i]]; + } + } + double _filter_number(double p_float); + String _get_component_type_name(const uint32_t p_component); + int _get_component_type_size(const int component_type); + Error _parse_scenes(Ref<GLTFState> state); + Error _parse_nodes(Ref<GLTFState> state); + String _get_type_name(const GLTFType p_component); + String _get_accessor_type_name(const GLTFDocument::GLTFType p_type); + String _sanitize_scene_name(const String &name); + String _gen_unique_name(Ref<GLTFState> state, const String &p_name); + String _sanitize_bone_name(const String &name); + String _gen_unique_bone_name(Ref<GLTFState> state, + const GLTFSkeletonIndex skel_i, + const String &p_name); + GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture); + Ref<Texture2D> _get_texture(Ref<GLTFState> state, + const GLTFTextureIndex p_texture); + Error _parse_json(const String &p_path, Ref<GLTFState> state); + Error _parse_glb(const String &p_path, Ref<GLTFState> state); + void _compute_node_heights(Ref<GLTFState> state); + Error _parse_buffers(Ref<GLTFState> state, const String &p_base_path); + Error _parse_buffer_views(Ref<GLTFState> state); + GLTFType _get_type_from_str(const String &p_string); + Error _parse_accessors(Ref<GLTFState> state); + Error _decode_buffer_view(Ref<GLTFState> state, double *dst, + const GLTFBufferViewIndex p_buffer_view, + const int skip_every, const int skip_bytes, + const int element_size, const int count, + const GLTFType type, const int component_count, + const int component_type, const int component_size, + const bool normalized, const int byte_offset, + const bool for_vertex); + Vector<double> _decode_accessor(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<float> _decode_accessor_as_floats(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<int> _decode_accessor_as_ints(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<Vector2> _decode_accessor_as_vec2(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<Vector3> _decode_accessor_as_vec3(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<Color> _decode_accessor_as_color(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<Quat> _decode_accessor_as_quat(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<Transform2D> _decode_accessor_as_xform2d(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<Basis> _decode_accessor_as_basis(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Vector<Transform> _decode_accessor_as_xform(Ref<GLTFState> state, + const GLTFAccessorIndex p_accessor, + const bool p_for_vertex); + Error _parse_meshes(Ref<GLTFState> state); + Error _serialize_textures(Ref<GLTFState> state); + Error _serialize_images(Ref<GLTFState> state, const String &p_path); + Error _serialize_lights(Ref<GLTFState> state); + Error _parse_images(Ref<GLTFState> state, const String &p_base_path); + Error _parse_textures(Ref<GLTFState> state); + Error _parse_materials(Ref<GLTFState> state); + void _set_texture_transform_uv1(const Dictionary &d, Ref<BaseMaterial3D> material); + void spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss, + Ref<BaseMaterial3D> p_material); + static void spec_gloss_to_metal_base_color(const Color &p_specular_factor, + const Color &p_diffuse, + Color &r_base_color, + float &r_metallic); + GLTFNodeIndex _find_highest_node(Ref<GLTFState> state, + const Vector<GLTFNodeIndex> &subset); + bool _capture_nodes_in_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin, + const GLTFNodeIndex node_index); + void _capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin); + Error _expand_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin); + Error _verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin); + Error _parse_skins(Ref<GLTFState> state); + Error _determine_skeletons(Ref<GLTFState> state); + Error _reparent_non_joint_skeleton_subtrees( + Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton, + const Vector<GLTFNodeIndex> &non_joints); + Error _reparent_to_fake_joint(Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton, + const GLTFNodeIndex node_index); + Error _determine_skeleton_roots(Ref<GLTFState> state, + const GLTFSkeletonIndex skel_i); + Error _create_skeletons(Ref<GLTFState> state); + Error _map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFState> state); + Error _serialize_skins(Ref<GLTFState> state); + Error _create_skins(Ref<GLTFState> state); + bool _skins_are_same(const Ref<Skin> skin_a, const Ref<Skin> skin_b); + void _remove_duplicate_skins(Ref<GLTFState> state); + Error _serialize_cameras(Ref<GLTFState> state); + Error _parse_cameras(Ref<GLTFState> state); + Error _parse_lights(Ref<GLTFState> state); + Error _parse_animations(Ref<GLTFState> state); + Error _serialize_animations(Ref<GLTFState> state); + BoneAttachment3D *_generate_bone_attachment(Ref<GLTFState> state, + Skeleton3D *skeleton, + const GLTFNodeIndex node_index); + EditorSceneImporterMeshNode3D *_generate_mesh_instance(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index); + Camera3D *_generate_camera(Ref<GLTFState> state, Node *scene_parent, + const GLTFNodeIndex node_index); + Light3D *_generate_light(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index); + Node3D *_generate_spatial(Ref<GLTFState> state, Node *scene_parent, + const GLTFNodeIndex node_index); + void _assign_scene_names(Ref<GLTFState> state); + template <class T> + T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, + const float p_time, + const GLTFAnimation::Interpolation p_interp); + GLTFAccessorIndex _encode_accessor_as_quats(Ref<GLTFState> state, + const Vector<Quat> p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_weights(Ref<GLTFState> state, + const Vector<Color> p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_joints(Ref<GLTFState> state, + const Vector<Color> p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_floats(Ref<GLTFState> state, + const Vector<real_t> p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_vec2(Ref<GLTFState> state, + const Vector<Vector2> p_attribs, + const bool p_for_vertex); + + void _calc_accessor_vec2_min_max(int i, const int element_count, Vector<double> &type_max, Vector2 attribs, Vector<double> &type_min) { + if (i == 0) { + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = attribs[(i * element_count) + type_i]; + type_min.write[type_i] = attribs[(i * element_count) + type_i]; + } + } + for (int32_t type_i = 0; type_i < element_count; type_i++) { + type_max.write[type_i] = MAX(attribs[(i * element_count) + type_i], type_max[type_i]); + type_min.write[type_i] = MIN(attribs[(i * element_count) + type_i], type_min[type_i]); + type_max.write[type_i] = _filter_number(type_max.write[type_i]); + type_min.write[type_i] = _filter_number(type_min.write[type_i]); + } + } + + GLTFAccessorIndex _encode_accessor_as_vec3(Ref<GLTFState> state, + const Vector<Vector3> p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_color(Ref<GLTFState> state, + const Vector<Color> p_attribs, + const bool p_for_vertex); + + void _calc_accessor_min_max(int p_i, const int p_element_count, Vector<double> &p_type_max, Vector<double> p_attribs, Vector<double> &p_type_min); + + GLTFAccessorIndex _encode_accessor_as_ints(Ref<GLTFState> state, + const Vector<int32_t> p_attribs, + const bool p_for_vertex); + GLTFAccessorIndex _encode_accessor_as_xform(Ref<GLTFState> state, + const Vector<Transform> p_attribs, + const bool p_for_vertex); + Error _encode_buffer_view(Ref<GLTFState> state, const double *src, + const int count, const GLTFType type, + const int component_type, const bool normalized, + const int byte_offset, const bool for_vertex, + GLTFBufferViewIndex &r_accessor); + Error _encode_accessors(Ref<GLTFState> state); + Error _encode_buffer_views(Ref<GLTFState> state); + Error _serialize_materials(Ref<GLTFState> state); + Error _serialize_meshes(Ref<GLTFState> state); + Error _serialize_nodes(Ref<GLTFState> state); + Error _serialize_scenes(Ref<GLTFState> state); + String interpolation_to_string(const GLTFAnimation::Interpolation p_interp); + GLTFAnimation::Track _convert_animation_track(Ref<GLTFState> state, + GLTFAnimation::Track p_track, + Ref<Animation> p_animation, Transform p_bone_rest, + int32_t p_track_i, + GLTFNodeIndex p_node_i); + Error _encode_buffer_bins(Ref<GLTFState> state, const String &p_path); + Error _encode_buffer_glb(Ref<GLTFState> state, const String &p_path); + Error _serialize_bone_attachment(Ref<GLTFState> state); + Dictionary _serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material); + Dictionary _serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material); + Error _serialize_version(Ref<GLTFState> state); + Error _serialize_file(Ref<GLTFState> state, const String p_path); + Error _serialize_extensions(Ref<GLTFState> state) const; + +public: + // http://www.itu.int/rec/R-REC-BT.601 + // http://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf + static constexpr float R_BRIGHTNESS_COEFF = 0.299f; + static constexpr float G_BRIGHTNESS_COEFF = 0.587f; + static constexpr float B_BRIGHTNESS_COEFF = 0.114f; + +private: + // https://github.com/microsoft/glTF-SDK/blob/master/GLTFSDK/Source/PBRUtils.cpp#L9 + // https://bghgary.github.io/glTF/convert-between-workflows-bjs/js/babylon.pbrUtilities.js + static float solve_metallic(float p_dielectric_specular, float diffuse, + float specular, + float p_one_minus_specular_strength); + static float get_perceived_brightness(const Color p_color); + static float get_max_component(const Color &p_color); + +public: + void _process_mesh_instances(Ref<GLTFState> state, Node *scene_root); + void _generate_scene_node(Ref<GLTFState> state, Node *scene_parent, + Node3D *scene_root, + const GLTFNodeIndex node_index); + void _import_animation(Ref<GLTFState> state, AnimationPlayer *ap, + const GLTFAnimationIndex index, const int bake_fps); + GLTFMeshIndex _convert_mesh_instance(Ref<GLTFState> state, + MeshInstance3D *p_mesh_instance); + void _convert_mesh_instances(Ref<GLTFState> state); + GLTFCameraIndex _convert_camera(Ref<GLTFState> state, Camera3D *p_camera); + void _convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Node3D *spatial, Ref<GLTFNode> gltf_node); + GLTFLightIndex _convert_light(Ref<GLTFState> state, Light3D *p_light); + GLTFSkeletonIndex _convert_skeleton(Ref<GLTFState> state, Skeleton3D *p_skeleton); + void _convert_spatial(Ref<GLTFState> state, Node3D *p_spatial, Ref<GLTFNode> p_node); + void _convert_scene_node(Ref<GLTFState> state, Node *p_current, Node *p_root, + const GLTFNodeIndex p_gltf_current, + const GLTFNodeIndex p_gltf_root); + + void _convert_csg_shape_to_gltf(Node *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> gltf_node, Ref<GLTFState> state); + + void _create_gltf_node(Ref<GLTFState> state, + Node *p_scene_parent, + GLTFNodeIndex current_node_i, + GLTFNodeIndex p_parent_node_index, + GLTFNodeIndex p_root_gltf_node, + Ref<GLTFNode> gltf_node); + void _convert_animation_player_to_gltf( + AnimationPlayer *animation_player, Ref<GLTFState> state, + const GLTFNodeIndex &p_gltf_current, + const GLTFNodeIndex &p_gltf_root_index, + Ref<GLTFNode> p_gltf_node, Node *p_scene_parent, + Node *p_root); + void _check_visibility(Node *p_node, bool &retflag); + void _convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> state, + Node3D *spatial, + Ref<GLTFNode> gltf_node); + void _convert_grid_map_to_gltf( + Node *p_scene_parent, + const GLTFNodeIndex &p_parent_node_index, + const GLTFNodeIndex &p_root_node_index, + Ref<GLTFNode> gltf_node, Ref<GLTFState> state, + Node *p_root_node); + void _convert_mult_mesh_instance_to_gltf( + Node *p_scene_parent, + const GLTFNodeIndex &p_parent_node_index, + const GLTFNodeIndex &p_root_node_index, + Ref<GLTFNode> gltf_node, Ref<GLTFState> state, + Node *p_root_node); + void _convert_skeleton_to_gltf( + Node *p_scene_parent, Ref<GLTFState> state, + const GLTFNodeIndex &p_parent_node_index, + const GLTFNodeIndex &p_root_node_index, + Ref<GLTFNode> gltf_node, Node *p_root_node); + void _convert_bone_attachment_to_gltf(Node *p_scene_parent, + Ref<GLTFState> state, + Ref<GLTFNode> gltf_node, + bool &retflag); + void _convert_mesh_to_gltf(Node *p_scene_parent, + Ref<GLTFState> state, Node3D *spatial, + Ref<GLTFNode> gltf_node); + void _convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, + String p_animation_track_name); + Error serialize(Ref<GLTFState> state, Node *p_root, const String &p_path); + Error parse(Ref<GLTFState> state, String p_paths, bool p_read_binary = false); +}; + +#endif // GLTF_DOCUMENT_H diff --git a/modules/gltf/gltf_light.cpp b/modules/gltf/gltf_light.cpp new file mode 100644 index 0000000000..da0e504474 --- /dev/null +++ b/modules/gltf/gltf_light.cpp @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* gltf_light.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_light.h" + +void GLTFLight::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_color"), &GLTFLight::get_color); + ClassDB::bind_method(D_METHOD("set_color", "color"), &GLTFLight::set_color); + ClassDB::bind_method(D_METHOD("get_intensity"), &GLTFLight::get_intensity); + ClassDB::bind_method(D_METHOD("set_intensity", "intensity"), &GLTFLight::set_intensity); + ClassDB::bind_method(D_METHOD("get_type"), &GLTFLight::get_type); + ClassDB::bind_method(D_METHOD("set_type", "type"), &GLTFLight::set_type); + ClassDB::bind_method(D_METHOD("get_range"), &GLTFLight::get_range); + ClassDB::bind_method(D_METHOD("set_range", "range"), &GLTFLight::set_range); + ClassDB::bind_method(D_METHOD("get_inner_cone_angle"), &GLTFLight::get_inner_cone_angle); + ClassDB::bind_method(D_METHOD("set_inner_cone_angle", "inner_cone_angle"), &GLTFLight::set_inner_cone_angle); + ClassDB::bind_method(D_METHOD("get_outer_cone_angle"), &GLTFLight::get_outer_cone_angle); + ClassDB::bind_method(D_METHOD("set_outer_cone_angle", "outer_cone_angle"), &GLTFLight::set_outer_cone_angle); + + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); // Color + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "intensity"), "set_intensity", "get_intensity"); // float + ADD_PROPERTY(PropertyInfo(Variant::STRING, "type"), "set_type", "get_type"); // String + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "range"), "set_range", "get_range"); // float + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inner_cone_angle"), "set_inner_cone_angle", "get_inner_cone_angle"); // float + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_cone_angle"), "set_outer_cone_angle", "get_outer_cone_angle"); // float +} + +Color GLTFLight::get_color() { + return color; +} + +void GLTFLight::set_color(Color p_color) { + color = p_color; +} + +float GLTFLight::get_intensity() { + return intensity; +} + +void GLTFLight::set_intensity(float p_intensity) { + intensity = p_intensity; +} + +String GLTFLight::get_type() { + return type; +} + +void GLTFLight::set_type(String p_type) { + type = p_type; +} + +float GLTFLight::get_range() { + return range; +} + +void GLTFLight::set_range(float p_range) { + range = p_range; +} + +float GLTFLight::get_inner_cone_angle() { + return inner_cone_angle; +} + +void GLTFLight::set_inner_cone_angle(float p_inner_cone_angle) { + inner_cone_angle = p_inner_cone_angle; +} + +float GLTFLight::get_outer_cone_angle() { + return outer_cone_angle; +} + +void GLTFLight::set_outer_cone_angle(float p_outer_cone_angle) { + outer_cone_angle = p_outer_cone_angle; +} diff --git a/modules/gltf/gltf_light.h b/modules/gltf/gltf_light.h new file mode 100644 index 0000000000..966ef5dd44 --- /dev/null +++ b/modules/gltf/gltf_light.h @@ -0,0 +1,72 @@ +/*************************************************************************/ +/* gltf_light.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_LIGHT_H +#define GLTF_LIGHT_H + +#include "core/config/engine.h" +#include "core/io/resource.h" + +class GLTFLight : public Resource { + GDCLASS(GLTFLight, Resource) + friend class GLTFDocument; + +protected: + static void _bind_methods(); + +private: + Color color; + float intensity = 0.0f; + String type; + float range = 0.0f; + float inner_cone_angle = 0.0f; + float outer_cone_angle = 0.0f; + +public: + Color get_color(); + void set_color(Color p_color); + + float get_intensity(); + void set_intensity(float p_intensity); + + String get_type(); + void set_type(String p_type); + + float get_range(); + void set_range(float p_range); + + float get_inner_cone_angle(); + void set_inner_cone_angle(float p_inner_cone_angle); + + float get_outer_cone_angle(); + void set_outer_cone_angle(float p_outer_cone_angle); +}; + +#endif // GLTF_LIGHT_H diff --git a/modules/gltf/gltf_mesh.cpp b/modules/gltf/gltf_mesh.cpp new file mode 100644 index 0000000000..09995faab3 --- /dev/null +++ b/modules/gltf/gltf_mesh.cpp @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* gltf_mesh.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_mesh.h" +#include "editor/import/scene_importer_mesh.h" + +void GLTFMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_mesh"), &GLTFMesh::get_mesh); + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &GLTFMesh::set_mesh); + ClassDB::bind_method(D_METHOD("get_blend_weights"), &GLTFMesh::get_blend_weights); + ClassDB::bind_method(D_METHOD("set_blend_weights", "blend_weights"), &GLTFMesh::set_blend_weights); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh"), "set_mesh", "get_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "blend_weights"), "set_blend_weights", "get_blend_weights"); // Vector<float> +} + +Ref<EditorSceneImporterMesh> GLTFMesh::get_mesh() { + return mesh; +} + +void GLTFMesh::set_mesh(Ref<EditorSceneImporterMesh> p_mesh) { + mesh = p_mesh; +} + +Vector<float> GLTFMesh::get_blend_weights() { + return blend_weights; +} + +void GLTFMesh::set_blend_weights(Vector<float> p_blend_weights) { + blend_weights = p_blend_weights; +} diff --git a/modules/gltf/gltf_mesh.h b/modules/gltf/gltf_mesh.h new file mode 100644 index 0000000000..5fb3069ad2 --- /dev/null +++ b/modules/gltf/gltf_mesh.h @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* gltf_mesh.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_MESH_H +#define GLTF_MESH_H + +#include "core/io/resource.h" +#include "editor/import/resource_importer_scene.h" +#include "editor/import/scene_importer_mesh.h" +#include "scene/resources/mesh.h" + +class GLTFMesh : public Resource { + GDCLASS(GLTFMesh, Resource); + +private: + Ref<EditorSceneImporterMesh> mesh; + Vector<float> blend_weights; + +protected: + static void _bind_methods(); + +public: + Ref<EditorSceneImporterMesh> get_mesh(); + void set_mesh(Ref<EditorSceneImporterMesh> p_mesh); + Vector<float> get_blend_weights(); + void set_blend_weights(Vector<float> p_blend_weights); +}; +#endif // GLTF_MESH_H diff --git a/modules/gltf/gltf_node.cpp b/modules/gltf/gltf_node.cpp new file mode 100644 index 0000000000..2fbd3f85d4 --- /dev/null +++ b/modules/gltf/gltf_node.cpp @@ -0,0 +1,189 @@ +/*************************************************************************/ +/* gltf_node.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_node.h" + +void GLTFNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_parent"), &GLTFNode::get_parent); + ClassDB::bind_method(D_METHOD("set_parent", "parent"), &GLTFNode::set_parent); + ClassDB::bind_method(D_METHOD("get_height"), &GLTFNode::get_height); + ClassDB::bind_method(D_METHOD("set_height", "height"), &GLTFNode::set_height); + ClassDB::bind_method(D_METHOD("get_xform"), &GLTFNode::get_xform); + ClassDB::bind_method(D_METHOD("set_xform", "xform"), &GLTFNode::set_xform); + ClassDB::bind_method(D_METHOD("get_mesh"), &GLTFNode::get_mesh); + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &GLTFNode::set_mesh); + ClassDB::bind_method(D_METHOD("get_camera"), &GLTFNode::get_camera); + ClassDB::bind_method(D_METHOD("set_camera", "camera"), &GLTFNode::set_camera); + ClassDB::bind_method(D_METHOD("get_skin"), &GLTFNode::get_skin); + ClassDB::bind_method(D_METHOD("set_skin", "skin"), &GLTFNode::set_skin); + ClassDB::bind_method(D_METHOD("get_skeleton"), &GLTFNode::get_skeleton); + ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &GLTFNode::set_skeleton); + ClassDB::bind_method(D_METHOD("get_joint"), &GLTFNode::get_joint); + ClassDB::bind_method(D_METHOD("set_joint", "joint"), &GLTFNode::set_joint); + ClassDB::bind_method(D_METHOD("get_translation"), &GLTFNode::get_translation); + ClassDB::bind_method(D_METHOD("set_translation", "translation"), &GLTFNode::set_translation); + ClassDB::bind_method(D_METHOD("get_rotation"), &GLTFNode::get_rotation); + ClassDB::bind_method(D_METHOD("set_rotation", "rotation"), &GLTFNode::set_rotation); + ClassDB::bind_method(D_METHOD("get_scale"), &GLTFNode::get_scale); + ClassDB::bind_method(D_METHOD("set_scale", "scale"), &GLTFNode::set_scale); + ClassDB::bind_method(D_METHOD("get_children"), &GLTFNode::get_children); + ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children); + ClassDB::bind_method(D_METHOD("get_fake_joint_parent"), &GLTFNode::get_fake_joint_parent); + ClassDB::bind_method(D_METHOD("set_fake_joint_parent", "fake_joint_parent"), &GLTFNode::set_fake_joint_parent); + ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light); + ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height"); // int + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "xform"), "set_xform", "get_xform"); // Transform + ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh"), "set_mesh", "get_mesh"); // GLTFMeshIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "camera"), "set_camera", "get_camera"); // GLTFCameraIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "skin"), "set_skin", "get_skin"); // GLTFSkinIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton"), "set_skeleton", "get_skeleton"); // GLTFSkeletonIndex + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "joint"), "set_joint", "get_joint"); // bool + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "translation"), "set_translation", "get_translation"); // Vector3 + ADD_PROPERTY(PropertyInfo(Variant::QUAT, "rotation"), "set_rotation", "get_rotation"); // Quat + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); // Vector3 + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "children"), "set_children", "get_children"); // Vector<int> + ADD_PROPERTY(PropertyInfo(Variant::INT, "fake_joint_parent"), "set_fake_joint_parent", "get_fake_joint_parent"); // GLTFNodeIndex + ADD_PROPERTY(PropertyInfo(Variant::INT, "light"), "set_light", "get_light"); // GLTFLightIndex +} + +GLTFNodeIndex GLTFNode::get_parent() { + return parent; +} + +void GLTFNode::set_parent(GLTFNodeIndex p_parent) { + parent = p_parent; +} + +int GLTFNode::get_height() { + return height; +} + +void GLTFNode::set_height(int p_height) { + height = p_height; +} + +Transform GLTFNode::get_xform() { + return xform; +} + +void GLTFNode::set_xform(Transform p_xform) { + xform = p_xform; +} + +GLTFMeshIndex GLTFNode::get_mesh() { + return mesh; +} + +void GLTFNode::set_mesh(GLTFMeshIndex p_mesh) { + mesh = p_mesh; +} + +GLTFCameraIndex GLTFNode::get_camera() { + return camera; +} + +void GLTFNode::set_camera(GLTFCameraIndex p_camera) { + camera = p_camera; +} + +GLTFSkinIndex GLTFNode::get_skin() { + return skin; +} + +void GLTFNode::set_skin(GLTFSkinIndex p_skin) { + skin = p_skin; +} + +GLTFSkeletonIndex GLTFNode::get_skeleton() { + return skeleton; +} + +void GLTFNode::set_skeleton(GLTFSkeletonIndex p_skeleton) { + skeleton = p_skeleton; +} + +bool GLTFNode::get_joint() { + return joint; +} + +void GLTFNode::set_joint(bool p_joint) { + joint = p_joint; +} + +Vector3 GLTFNode::get_translation() { + return translation; +} + +void GLTFNode::set_translation(Vector3 p_translation) { + translation = p_translation; +} + +Quat GLTFNode::get_rotation() { + return rotation; +} + +void GLTFNode::set_rotation(Quat p_rotation) { + rotation = p_rotation; +} + +Vector3 GLTFNode::get_scale() { + return scale; +} + +void GLTFNode::set_scale(Vector3 p_scale) { + scale = p_scale; +} + +Vector<int> GLTFNode::get_children() { + return children; +} + +void GLTFNode::set_children(Vector<int> p_children) { + children = p_children; +} + +GLTFNodeIndex GLTFNode::get_fake_joint_parent() { + return fake_joint_parent; +} + +void GLTFNode::set_fake_joint_parent(GLTFNodeIndex p_fake_joint_parent) { + fake_joint_parent = p_fake_joint_parent; +} + +GLTFLightIndex GLTFNode::get_light() { + return light; +} + +void GLTFNode::set_light(GLTFLightIndex p_light) { + light = p_light; +} diff --git a/modules/gltf/gltf_node.h b/modules/gltf/gltf_node.h new file mode 100644 index 0000000000..b96e700ec2 --- /dev/null +++ b/modules/gltf/gltf_node.h @@ -0,0 +1,105 @@ +/*************************************************************************/ +/* gltf_node.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_NODE_H +#define GLTF_NODE_H + +#include "core/io/resource.h" +#include "gltf_document.h" + +class GLTFNode : public Resource { + GDCLASS(GLTFNode, Resource); + friend class GLTFDocument; + friend class PackedSceneGLTF; + +private: + // matrices need to be transformed to this + GLTFNodeIndex parent = -1; + int height = -1; + Transform xform; + GLTFMeshIndex mesh = -1; + GLTFCameraIndex camera = -1; + GLTFSkinIndex skin = -1; + GLTFSkeletonIndex skeleton = -1; + bool joint = false; + Vector3 translation; + Quat rotation; + Vector3 scale = Vector3(1, 1, 1); + Vector<int> children; + GLTFNodeIndex fake_joint_parent = -1; + GLTFLightIndex light = -1; + +protected: + static void _bind_methods(); + +public: + GLTFNodeIndex get_parent(); + void set_parent(GLTFNodeIndex p_parent); + + int get_height(); + void set_height(int p_height); + + Transform get_xform(); + void set_xform(Transform p_xform); + + GLTFMeshIndex get_mesh(); + void set_mesh(GLTFMeshIndex p_mesh); + + GLTFCameraIndex get_camera(); + void set_camera(GLTFCameraIndex p_camera); + + GLTFSkinIndex get_skin(); + void set_skin(GLTFSkinIndex p_skin); + + GLTFSkeletonIndex get_skeleton(); + void set_skeleton(GLTFSkeletonIndex p_skeleton); + + bool get_joint(); + void set_joint(bool p_joint); + + Vector3 get_translation(); + void set_translation(Vector3 p_translation); + + Quat get_rotation(); + void set_rotation(Quat p_rotation); + + Vector3 get_scale(); + void set_scale(Vector3 p_scale); + + Vector<int> get_children(); + void set_children(Vector<int> p_children); + + GLTFNodeIndex get_fake_joint_parent(); + void set_fake_joint_parent(GLTFNodeIndex p_fake_joint_parent); + + GLTFLightIndex get_light(); + void set_light(GLTFLightIndex p_light); +}; +#endif // GLTF_NODE_H diff --git a/modules/gltf/gltf_skeleton.cpp b/modules/gltf/gltf_skeleton.cpp new file mode 100644 index 0000000000..35671335d3 --- /dev/null +++ b/modules/gltf/gltf_skeleton.cpp @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* gltf_skeleton.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_skeleton.h" + +void GLTFSkeleton::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_joints"), &GLTFSkeleton::get_joints); + ClassDB::bind_method(D_METHOD("set_joints", "joints"), &GLTFSkeleton::set_joints); + ClassDB::bind_method(D_METHOD("get_roots"), &GLTFSkeleton::get_roots); + ClassDB::bind_method(D_METHOD("set_roots", "roots"), &GLTFSkeleton::set_roots); + ClassDB::bind_method(D_METHOD("get_godot_skeleton"), &GLTFSkeleton::get_godot_skeleton); + ClassDB::bind_method(D_METHOD("get_unique_names"), &GLTFSkeleton::get_unique_names); + ClassDB::bind_method(D_METHOD("set_unique_names", "unique_names"), &GLTFSkeleton::set_unique_names); + ClassDB::bind_method(D_METHOD("get_godot_bone_node"), &GLTFSkeleton::get_godot_bone_node); + ClassDB::bind_method(D_METHOD("set_godot_bone_node", "godot_bone_node"), &GLTFSkeleton::set_godot_bone_node); + ClassDB::bind_method(D_METHOD("get_bone_attachment_count"), &GLTFSkeleton::get_bone_attachment_count); + ClassDB::bind_method(D_METHOD("get_bone_attachment"), &GLTFSkeleton::get_bone_attachment); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "joints"), "set_joints", "get_joints"); // Vector<GLTFNodeIndex> + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "roots"), "set_roots", "get_roots"); // Vector<GLTFNodeIndex> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "unique_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_unique_names", "get_unique_names"); // Set<String> + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "godot_bone_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_godot_bone_node", "get_godot_bone_node"); // Map<int32_t, +} + +Vector<GLTFNodeIndex> GLTFSkeleton::get_joints() { + return joints; +} + +void GLTFSkeleton::set_joints(Vector<GLTFNodeIndex> p_joints) { + joints = p_joints; +} + +Vector<GLTFNodeIndex> GLTFSkeleton::get_roots() { + return roots; +} + +void GLTFSkeleton::set_roots(Vector<GLTFNodeIndex> p_roots) { + roots = p_roots; +} + +Skeleton3D *GLTFSkeleton::get_godot_skeleton() { + return godot_skeleton; +} + +Array GLTFSkeleton::get_unique_names() { + return GLTFDocument::to_array(unique_names); +} + +void GLTFSkeleton::set_unique_names(Array p_unique_names) { + GLTFDocument::set_from_array(unique_names, p_unique_names); +} + +Dictionary GLTFSkeleton::get_godot_bone_node() { + return GLTFDocument::to_dict(godot_bone_node); +} + +void GLTFSkeleton::set_godot_bone_node(Dictionary p_indict) { + GLTFDocument::set_from_dict(godot_bone_node, p_indict); +} + +BoneAttachment3D *GLTFSkeleton::get_bone_attachment(int idx) { + ERR_FAIL_INDEX_V(idx, bone_attachments.size(), nullptr); + return bone_attachments[idx]; +} + +int32_t GLTFSkeleton::get_bone_attachment_count() { + return bone_attachments.size(); +} diff --git a/modules/gltf/gltf_skeleton.h b/modules/gltf/gltf_skeleton.h new file mode 100644 index 0000000000..6263fa3c5d --- /dev/null +++ b/modules/gltf/gltf_skeleton.h @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* gltf_skeleton.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_SKELETON_H +#define GLTF_SKELETON_H + +#include "core/io/resource.h" +#include "gltf_document.h" + +class GLTFSkeleton : public Resource { + GDCLASS(GLTFSkeleton, Resource); + friend class GLTFDocument; + +private: + // The *synthesized* skeletons joints + Vector<GLTFNodeIndex> joints; + + // The roots of the skeleton. If there are multiple, each root must have the + // same parent (ie roots are siblings) + Vector<GLTFNodeIndex> roots; + + // The created Skeleton3D for the scene + Skeleton3D *godot_skeleton = nullptr; + + // Set of unique bone names for the skeleton + Set<String> unique_names; + + Map<int32_t, GLTFNodeIndex> godot_bone_node; + + Vector<BoneAttachment3D *> bone_attachments; + +protected: + static void _bind_methods(); + +public: + Vector<GLTFNodeIndex> get_joints(); + void set_joints(Vector<GLTFNodeIndex> p_joints); + + Vector<GLTFNodeIndex> get_roots(); + void set_roots(Vector<GLTFNodeIndex> p_roots); + + Skeleton3D *get_godot_skeleton(); + + // Skeleton *get_godot_skeleton() { + // return this->godot_skeleton; + // } + // void set_godot_skeleton(Skeleton p_*godot_skeleton) { + // this->godot_skeleton = p_godot_skeleton; + // } + + Array get_unique_names(); + void set_unique_names(Array p_unique_names); + + //Map<int32_t, GLTFNodeIndex> get_godot_bone_node() { + // return this->godot_bone_node; + //} + //void set_godot_bone_node(Map<int32_t, GLTFNodeIndex> p_godot_bone_node) { + // this->godot_bone_node = p_godot_bone_node; + //} + Dictionary get_godot_bone_node(); + void set_godot_bone_node(Dictionary p_indict); + + //Dictionary get_godot_bone_node() { + // return VariantConversion::to_dict(this->godot_bone_node); + //} + //void set_godot_bone_node(Dictionary p_indict) { + // VariantConversion::set_from_dict(this->godot_bone_node, p_indict); + //} + + BoneAttachment3D *get_bone_attachment(int idx); + + int32_t get_bone_attachment_count(); +}; +#endif // GLTF_SKELETON_H diff --git a/modules/gltf/gltf_skin.cpp b/modules/gltf/gltf_skin.cpp new file mode 100644 index 0000000000..1b94b9d106 --- /dev/null +++ b/modules/gltf/gltf_skin.cpp @@ -0,0 +1,155 @@ +/*************************************************************************/ +/* gltf_skin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_skin.h" + +void GLTFSkin::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_skin_root"), &GLTFSkin::get_skin_root); + ClassDB::bind_method(D_METHOD("set_skin_root", "skin_root"), &GLTFSkin::set_skin_root); + ClassDB::bind_method(D_METHOD("get_joints_original"), &GLTFSkin::get_joints_original); + ClassDB::bind_method(D_METHOD("set_joints_original", "joints_original"), &GLTFSkin::set_joints_original); + ClassDB::bind_method(D_METHOD("get_inverse_binds"), &GLTFSkin::get_inverse_binds); + ClassDB::bind_method(D_METHOD("set_inverse_binds", "inverse_binds"), &GLTFSkin::set_inverse_binds); + ClassDB::bind_method(D_METHOD("get_joints"), &GLTFSkin::get_joints); + ClassDB::bind_method(D_METHOD("set_joints", "joints"), &GLTFSkin::set_joints); + ClassDB::bind_method(D_METHOD("get_non_joints"), &GLTFSkin::get_non_joints); + ClassDB::bind_method(D_METHOD("set_non_joints", "non_joints"), &GLTFSkin::set_non_joints); + ClassDB::bind_method(D_METHOD("get_roots"), &GLTFSkin::get_roots); + ClassDB::bind_method(D_METHOD("set_roots", "roots"), &GLTFSkin::set_roots); + ClassDB::bind_method(D_METHOD("get_skeleton"), &GLTFSkin::get_skeleton); + ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &GLTFSkin::set_skeleton); + ClassDB::bind_method(D_METHOD("get_joint_i_to_bone_i"), &GLTFSkin::get_joint_i_to_bone_i); + ClassDB::bind_method(D_METHOD("set_joint_i_to_bone_i", "joint_i_to_bone_i"), &GLTFSkin::set_joint_i_to_bone_i); + ClassDB::bind_method(D_METHOD("get_joint_i_to_name"), &GLTFSkin::get_joint_i_to_name); + ClassDB::bind_method(D_METHOD("set_joint_i_to_name", "joint_i_to_name"), &GLTFSkin::set_joint_i_to_name); + ClassDB::bind_method(D_METHOD("get_godot_skin"), &GLTFSkin::get_godot_skin); + ClassDB::bind_method(D_METHOD("set_godot_skin", "godot_skin"), &GLTFSkin::set_godot_skin); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "skin_root"), "set_skin_root", "get_skin_root"); // GLTFNodeIndex + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "joints_original"), "set_joints_original", "get_joints_original"); // Vector<GLTFNodeIndex> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "inverse_binds", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "set_inverse_binds", "get_inverse_binds"); // Vector<Transform> + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "joints"), "set_joints", "get_joints"); // Vector<GLTFNodeIndex> + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "non_joints"), "set_non_joints", "get_non_joints"); // Vector<GLTFNodeIndex> + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "roots"), "set_roots", "get_roots"); // Vector<GLTFNodeIndex> + ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton"), "set_skeleton", "get_skeleton"); // int + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "joint_i_to_bone_i", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "set_joint_i_to_bone_i", "get_joint_i_to_bone_i"); // Map<int, + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "joint_i_to_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "set_joint_i_to_name", "get_joint_i_to_name"); // Map<int, + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "godot_skin"), "set_godot_skin", "get_godot_skin"); // Ref<Skin> +} + +GLTFNodeIndex GLTFSkin::get_skin_root() { + return skin_root; +} + +void GLTFSkin::set_skin_root(GLTFNodeIndex p_skin_root) { + skin_root = p_skin_root; +} + +Vector<GLTFNodeIndex> GLTFSkin::get_joints_original() { + return joints_original; +} + +void GLTFSkin::set_joints_original(Vector<GLTFNodeIndex> p_joints_original) { + joints_original = p_joints_original; +} + +Array GLTFSkin::get_inverse_binds() { + return GLTFDocument::to_array(inverse_binds); +} + +void GLTFSkin::set_inverse_binds(Array p_inverse_binds) { + GLTFDocument::set_from_array(inverse_binds, p_inverse_binds); +} + +Vector<GLTFNodeIndex> GLTFSkin::get_joints() { + return joints; +} + +void GLTFSkin::set_joints(Vector<GLTFNodeIndex> p_joints) { + joints = p_joints; +} + +Vector<GLTFNodeIndex> GLTFSkin::get_non_joints() { + return non_joints; +} + +void GLTFSkin::set_non_joints(Vector<GLTFNodeIndex> p_non_joints) { + non_joints = p_non_joints; +} + +Vector<GLTFNodeIndex> GLTFSkin::get_roots() { + return roots; +} + +void GLTFSkin::set_roots(Vector<GLTFNodeIndex> p_roots) { + roots = p_roots; +} + +int GLTFSkin::get_skeleton() { + return skeleton; +} + +void GLTFSkin::set_skeleton(int p_skeleton) { + skeleton = p_skeleton; +} + +Dictionary GLTFSkin::get_joint_i_to_bone_i() { + return GLTFDocument::to_dict(joint_i_to_bone_i); +} + +void GLTFSkin::set_joint_i_to_bone_i(Dictionary p_joint_i_to_bone_i) { + GLTFDocument::set_from_dict(joint_i_to_bone_i, p_joint_i_to_bone_i); +} + +Dictionary GLTFSkin::get_joint_i_to_name() { + Dictionary ret; + Map<int, StringName>::Element *elem = joint_i_to_name.front(); + while (elem) { + ret[elem->key()] = String(elem->value()); + elem = elem->next(); + } + return ret; +} + +void GLTFSkin::set_joint_i_to_name(Dictionary p_joint_i_to_name) { + joint_i_to_name = Map<int, StringName>(); + Array keys = p_joint_i_to_name.keys(); + for (int i = 0; i < keys.size(); i++) { + joint_i_to_name[keys[i]] = joint_i_to_name[keys[i]]; + } +} + +Ref<Skin> GLTFSkin::get_godot_skin() { + return godot_skin; +} + +void GLTFSkin::set_godot_skin(Ref<Skin> p_godot_skin) { + godot_skin = p_godot_skin; +} diff --git a/modules/gltf/gltf_skin.h b/modules/gltf/gltf_skin.h new file mode 100644 index 0000000000..09e1a37a55 --- /dev/null +++ b/modules/gltf/gltf_skin.h @@ -0,0 +1,109 @@ +/*************************************************************************/ +/* gltf_skin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_SKIN_H +#define GLTF_SKIN_H + +#include "core/io/resource.h" +#include "gltf_document.h" + +class GLTFSkin : public Resource { + GDCLASS(GLTFSkin, Resource); + friend class GLTFDocument; + +private: + // The "skeleton" property defined in the gltf spec. -1 = Scene Root + GLTFNodeIndex skin_root = -1; + + Vector<GLTFNodeIndex> joints_original; + Vector<Transform> inverse_binds; + + // Note: joints + non_joints should form a complete subtree, or subtrees + // with a common parent + + // All nodes that are skins that are caught in-between the original joints + // (inclusive of joints_original) + Vector<GLTFNodeIndex> joints; + + // All Nodes that are caught in-between skin joint nodes, and are not + // defined as joints by any skin + Vector<GLTFNodeIndex> non_joints; + + // The roots of the skin. In the case of multiple roots, their parent *must* + // be the same (the roots must be siblings) + Vector<GLTFNodeIndex> roots; + + // The GLTF Skeleton this Skin points to (after we determine skeletons) + GLTFSkeletonIndex skeleton = -1; + + // A mapping from the joint indices (in the order of joints_original) to the + // Godot Skeleton's bone_indices + Map<int, int> joint_i_to_bone_i; + Map<int, StringName> joint_i_to_name; + + // The Actual Skin that will be created as a mapping between the IBM's of + // this skin to the generated skeleton for the mesh instances. + Ref<Skin> godot_skin; + +protected: + static void _bind_methods(); + +public: + GLTFNodeIndex get_skin_root(); + void set_skin_root(GLTFNodeIndex p_skin_root); + + Vector<GLTFNodeIndex> get_joints_original(); + void set_joints_original(Vector<GLTFNodeIndex> p_joints_original); + + Array get_inverse_binds(); + void set_inverse_binds(Array p_inverse_binds); + + Vector<GLTFNodeIndex> get_joints(); + void set_joints(Vector<GLTFNodeIndex> p_joints); + + Vector<GLTFNodeIndex> get_non_joints(); + void set_non_joints(Vector<GLTFNodeIndex> p_non_joints); + + Vector<GLTFNodeIndex> get_roots(); + void set_roots(Vector<GLTFNodeIndex> p_roots); + + int get_skeleton(); + void set_skeleton(int p_skeleton); + + Dictionary get_joint_i_to_bone_i(); + void set_joint_i_to_bone_i(Dictionary p_joint_i_to_bone_i); + + Dictionary get_joint_i_to_name(); + void set_joint_i_to_name(Dictionary p_joint_i_to_name); + + Ref<Skin> get_godot_skin(); + void set_godot_skin(Ref<Skin> p_godot_skin); +}; +#endif // GLTF_SKIN_H diff --git a/modules/gltf/gltf_spec_gloss.cpp b/modules/gltf/gltf_spec_gloss.cpp new file mode 100644 index 0000000000..7f27805d62 --- /dev/null +++ b/modules/gltf/gltf_spec_gloss.cpp @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* gltf_spec_gloss.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_spec_gloss.h" + +void GLTFSpecGloss::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_diffuse_img"), &GLTFSpecGloss::get_diffuse_img); + ClassDB::bind_method(D_METHOD("set_diffuse_img", "diffuse_img"), &GLTFSpecGloss::set_diffuse_img); + ClassDB::bind_method(D_METHOD("get_diffuse_factor"), &GLTFSpecGloss::get_diffuse_factor); + ClassDB::bind_method(D_METHOD("set_diffuse_factor", "diffuse_factor"), &GLTFSpecGloss::set_diffuse_factor); + ClassDB::bind_method(D_METHOD("get_gloss_factor"), &GLTFSpecGloss::get_gloss_factor); + ClassDB::bind_method(D_METHOD("set_gloss_factor", "gloss_factor"), &GLTFSpecGloss::set_gloss_factor); + ClassDB::bind_method(D_METHOD("get_specular_factor"), &GLTFSpecGloss::get_specular_factor); + ClassDB::bind_method(D_METHOD("set_specular_factor", "specular_factor"), &GLTFSpecGloss::set_specular_factor); + ClassDB::bind_method(D_METHOD("get_spec_gloss_img"), &GLTFSpecGloss::get_spec_gloss_img); + ClassDB::bind_method(D_METHOD("set_spec_gloss_img", "spec_gloss_img"), &GLTFSpecGloss::set_spec_gloss_img); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "diffuse_img"), "set_diffuse_img", "get_diffuse_img"); // Ref<Image> + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "diffuse_factor"), "set_diffuse_factor", "get_diffuse_factor"); // Color + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gloss_factor"), "set_gloss_factor", "get_gloss_factor"); // float + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "specular_factor"), "set_specular_factor", "get_specular_factor"); // Color + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "spec_gloss_img"), "set_spec_gloss_img", "get_spec_gloss_img"); // Ref<Image> +} + +Ref<Image> GLTFSpecGloss::get_diffuse_img() { + return diffuse_img; +} + +void GLTFSpecGloss::set_diffuse_img(Ref<Image> p_diffuse_img) { + diffuse_img = p_diffuse_img; +} + +Color GLTFSpecGloss::get_diffuse_factor() { + return diffuse_factor; +} + +void GLTFSpecGloss::set_diffuse_factor(Color p_diffuse_factor) { + diffuse_factor = p_diffuse_factor; +} + +float GLTFSpecGloss::get_gloss_factor() { + return gloss_factor; +} + +void GLTFSpecGloss::set_gloss_factor(float p_gloss_factor) { + gloss_factor = p_gloss_factor; +} + +Color GLTFSpecGloss::get_specular_factor() { + return specular_factor; +} + +void GLTFSpecGloss::set_specular_factor(Color p_specular_factor) { + specular_factor = p_specular_factor; +} + +Ref<Image> GLTFSpecGloss::get_spec_gloss_img() { + return spec_gloss_img; +} + +void GLTFSpecGloss::set_spec_gloss_img(Ref<Image> p_spec_gloss_img) { + spec_gloss_img = p_spec_gloss_img; +} diff --git a/modules/gltf/gltf_spec_gloss.h b/modules/gltf/gltf_spec_gloss.h new file mode 100644 index 0000000000..e06c6c14f3 --- /dev/null +++ b/modules/gltf/gltf_spec_gloss.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* gltf_spec_gloss.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_SPEC_GLOSS_H +#define GLTF_SPEC_GLOSS_H + +#include "core/io/image.h" +#include "core/io/resource.h" + +class GLTFSpecGloss : public Resource { + GDCLASS(GLTFSpecGloss, Resource); + friend class GLTFDocument; + +private: + Ref<Image> diffuse_img = nullptr; + Color diffuse_factor = Color(1.0f, 1.0f, 1.0f); + float gloss_factor = 1.0f; + Color specular_factor = Color(1.0f, 1.0f, 1.0f); + Ref<Image> spec_gloss_img = nullptr; + +protected: + static void _bind_methods(); + +public: + Ref<Image> get_diffuse_img(); + void set_diffuse_img(Ref<Image> p_diffuse_img); + + Color get_diffuse_factor(); + void set_diffuse_factor(Color p_diffuse_factor); + + float get_gloss_factor(); + void set_gloss_factor(float p_gloss_factor); + + Color get_specular_factor(); + void set_specular_factor(Color p_specular_factor); + + Ref<Image> get_spec_gloss_img(); + void set_spec_gloss_img(Ref<Image> p_spec_gloss_img); +}; +#endif // GLTF_SPEC_GLOSS_H diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp new file mode 100644 index 0000000000..403ae26bd3 --- /dev/null +++ b/modules/gltf/gltf_state.cpp @@ -0,0 +1,296 @@ +/*************************************************************************/ +/* gltf_state.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_state.h" + +void GLTFState::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_json"), &GLTFState::get_json); + ClassDB::bind_method(D_METHOD("set_json", "json"), &GLTFState::set_json); + ClassDB::bind_method(D_METHOD("get_major_version"), &GLTFState::get_major_version); + ClassDB::bind_method(D_METHOD("set_major_version", "major_version"), &GLTFState::set_major_version); + ClassDB::bind_method(D_METHOD("get_minor_version"), &GLTFState::get_minor_version); + ClassDB::bind_method(D_METHOD("set_minor_version", "minor_version"), &GLTFState::set_minor_version); + ClassDB::bind_method(D_METHOD("get_glb_data"), &GLTFState::get_glb_data); + ClassDB::bind_method(D_METHOD("set_glb_data", "glb_data"), &GLTFState::set_glb_data); + ClassDB::bind_method(D_METHOD("get_use_named_skin_binds"), &GLTFState::get_use_named_skin_binds); + ClassDB::bind_method(D_METHOD("set_use_named_skin_binds", "use_named_skin_binds"), &GLTFState::set_use_named_skin_binds); + ClassDB::bind_method(D_METHOD("get_nodes"), &GLTFState::get_nodes); + ClassDB::bind_method(D_METHOD("set_nodes", "nodes"), &GLTFState::set_nodes); + ClassDB::bind_method(D_METHOD("get_buffers"), &GLTFState::get_buffers); + ClassDB::bind_method(D_METHOD("set_buffers", "buffers"), &GLTFState::set_buffers); + ClassDB::bind_method(D_METHOD("get_buffer_views"), &GLTFState::get_buffer_views); + ClassDB::bind_method(D_METHOD("set_buffer_views", "buffer_views"), &GLTFState::set_buffer_views); + ClassDB::bind_method(D_METHOD("get_accessors"), &GLTFState::get_accessors); + ClassDB::bind_method(D_METHOD("set_accessors", "accessors"), &GLTFState::set_accessors); + ClassDB::bind_method(D_METHOD("get_meshes"), &GLTFState::get_meshes); + ClassDB::bind_method(D_METHOD("set_meshes", "meshes"), &GLTFState::set_meshes); + ClassDB::bind_method(D_METHOD("get_animation_players_count"), &GLTFState::get_animation_players_count); + ClassDB::bind_method(D_METHOD("get_animation_player"), &GLTFState::get_animation_player); + ClassDB::bind_method(D_METHOD("get_materials"), &GLTFState::get_materials); + ClassDB::bind_method(D_METHOD("set_materials", "materials"), &GLTFState::set_materials); + ClassDB::bind_method(D_METHOD("get_scene_name"), &GLTFState::get_scene_name); + ClassDB::bind_method(D_METHOD("set_scene_name", "scene_name"), &GLTFState::set_scene_name); + ClassDB::bind_method(D_METHOD("get_root_nodes"), &GLTFState::get_root_nodes); + ClassDB::bind_method(D_METHOD("set_root_nodes", "root_nodes"), &GLTFState::set_root_nodes); + ClassDB::bind_method(D_METHOD("get_textures"), &GLTFState::get_textures); + ClassDB::bind_method(D_METHOD("set_textures", "textures"), &GLTFState::set_textures); + ClassDB::bind_method(D_METHOD("get_images"), &GLTFState::get_images); + ClassDB::bind_method(D_METHOD("set_images", "images"), &GLTFState::set_images); + ClassDB::bind_method(D_METHOD("get_skins"), &GLTFState::get_skins); + ClassDB::bind_method(D_METHOD("set_skins", "skins"), &GLTFState::set_skins); + ClassDB::bind_method(D_METHOD("get_cameras"), &GLTFState::get_cameras); + ClassDB::bind_method(D_METHOD("set_cameras", "cameras"), &GLTFState::set_cameras); + ClassDB::bind_method(D_METHOD("get_lights"), &GLTFState::get_lights); + ClassDB::bind_method(D_METHOD("set_lights", "lights"), &GLTFState::set_lights); + ClassDB::bind_method(D_METHOD("get_unique_names"), &GLTFState::get_unique_names); + ClassDB::bind_method(D_METHOD("set_unique_names", "unique_names"), &GLTFState::set_unique_names); + ClassDB::bind_method(D_METHOD("get_skeletons"), &GLTFState::get_skeletons); + ClassDB::bind_method(D_METHOD("set_skeletons", "skeletons"), &GLTFState::set_skeletons); + ClassDB::bind_method(D_METHOD("get_skeleton_to_node"), &GLTFState::get_skeleton_to_node); + ClassDB::bind_method(D_METHOD("set_skeleton_to_node", "skeleton_to_node"), &GLTFState::set_skeleton_to_node); + ClassDB::bind_method(D_METHOD("get_animations"), &GLTFState::get_animations); + ClassDB::bind_method(D_METHOD("set_animations", "animations"), &GLTFState::set_animations); + ClassDB::bind_method(D_METHOD("get_scene_node"), &GLTFState::get_scene_node); + + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "json"), "set_json", "get_json"); // Dictionary + ADD_PROPERTY(PropertyInfo(Variant::INT, "major_version"), "set_major_version", "get_major_version"); // int + ADD_PROPERTY(PropertyInfo(Variant::INT, "minor_version"), "set_minor_version", "get_minor_version"); // int + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "glb_data"), "set_glb_data", "get_glb_data"); // Vector<uint8_t> + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_named_skin_binds"), "set_use_named_skin_binds", "get_use_named_skin_binds"); // bool + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_nodes", "get_nodes"); // Vector<Ref<GLTFNode>> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "buffers"), "set_buffers", "get_buffers"); // Vector<Vector<uint8_t> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "buffer_views", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_buffer_views", "get_buffer_views"); // Vector<Ref<GLTFBufferView>> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "accessors", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_accessors", "get_accessors"); // Vector<Ref<GLTFAccessor>> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "meshes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_meshes", "get_meshes"); // Vector<Ref<GLTFMesh>> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "materials", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_materials", "get_materials"); // Vector<Ref<Material> + ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_name"), "set_scene_name", "get_scene_name"); // String + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "root_nodes"), "set_root_nodes", "get_root_nodes"); // Vector<int> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_textures", "get_textures"); // Vector<Ref<GLTFTexture>> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "images", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_images", "get_images"); // Vector<Ref<Texture> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skins", "get_skins"); // Vector<Ref<GLTFSkin>> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "cameras", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_cameras", "get_cameras"); // Vector<Ref<GLTFCamera>> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lights", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_lights", "get_lights"); // Vector<Ref<GLTFLight>> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "unique_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_unique_names", "get_unique_names"); // Set<String> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skeletons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeletons", "get_skeletons"); // Vector<Ref<GLTFSkeleton>> + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "skeleton_to_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeleton_to_node", "get_skeleton_to_node"); // Map<GLTFSkeletonIndex, + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector<Ref<GLTFAnimation>> +} + +Dictionary GLTFState::get_json() { + return json; +} + +void GLTFState::set_json(Dictionary p_json) { + json = p_json; +} + +int GLTFState::get_major_version() { + return major_version; +} + +void GLTFState::set_major_version(int p_major_version) { + major_version = p_major_version; +} + +int GLTFState::get_minor_version() { + return minor_version; +} + +void GLTFState::set_minor_version(int p_minor_version) { + minor_version = p_minor_version; +} + +Vector<uint8_t> GLTFState::get_glb_data() { + return glb_data; +} + +void GLTFState::set_glb_data(Vector<uint8_t> p_glb_data) { + glb_data = p_glb_data; +} + +bool GLTFState::get_use_named_skin_binds() { + return use_named_skin_binds; +} + +void GLTFState::set_use_named_skin_binds(bool p_use_named_skin_binds) { + use_named_skin_binds = p_use_named_skin_binds; +} + +Array GLTFState::get_nodes() { + return GLTFDocument::to_array(nodes); +} + +void GLTFState::set_nodes(Array p_nodes) { + GLTFDocument::set_from_array(nodes, p_nodes); +} + +Array GLTFState::get_buffers() { + return GLTFDocument::to_array(buffers); +} + +void GLTFState::set_buffers(Array p_buffers) { + GLTFDocument::set_from_array(buffers, p_buffers); +} + +Array GLTFState::get_buffer_views() { + return GLTFDocument::to_array(buffer_views); +} + +void GLTFState::set_buffer_views(Array p_buffer_views) { + GLTFDocument::set_from_array(buffer_views, p_buffer_views); +} + +Array GLTFState::get_accessors() { + return GLTFDocument::to_array(accessors); +} + +void GLTFState::set_accessors(Array p_accessors) { + GLTFDocument::set_from_array(accessors, p_accessors); +} + +Array GLTFState::get_meshes() { + return GLTFDocument::to_array(meshes); +} + +void GLTFState::set_meshes(Array p_meshes) { + GLTFDocument::set_from_array(meshes, p_meshes); +} + +Array GLTFState::get_materials() { + return GLTFDocument::to_array(materials); +} + +void GLTFState::set_materials(Array p_materials) { + GLTFDocument::set_from_array(materials, p_materials); +} + +String GLTFState::get_scene_name() { + return scene_name; +} + +void GLTFState::set_scene_name(String p_scene_name) { + scene_name = p_scene_name; +} + +Array GLTFState::get_root_nodes() { + return GLTFDocument::to_array(root_nodes); +} + +void GLTFState::set_root_nodes(Array p_root_nodes) { + GLTFDocument::set_from_array(root_nodes, p_root_nodes); +} + +Array GLTFState::get_textures() { + return GLTFDocument::to_array(textures); +} + +void GLTFState::set_textures(Array p_textures) { + GLTFDocument::set_from_array(textures, p_textures); +} + +Array GLTFState::get_images() { + return GLTFDocument::to_array(images); +} + +void GLTFState::set_images(Array p_images) { + GLTFDocument::set_from_array(images, p_images); +} + +Array GLTFState::get_skins() { + return GLTFDocument::to_array(skins); +} + +void GLTFState::set_skins(Array p_skins) { + GLTFDocument::set_from_array(skins, p_skins); +} + +Array GLTFState::get_cameras() { + return GLTFDocument::to_array(cameras); +} + +void GLTFState::set_cameras(Array p_cameras) { + GLTFDocument::set_from_array(cameras, p_cameras); +} + +Array GLTFState::get_lights() { + return GLTFDocument::to_array(lights); +} + +void GLTFState::set_lights(Array p_lights) { + GLTFDocument::set_from_array(lights, p_lights); +} + +Array GLTFState::get_unique_names() { + return GLTFDocument::to_array(unique_names); +} + +void GLTFState::set_unique_names(Array p_unique_names) { + GLTFDocument::set_from_array(unique_names, p_unique_names); +} + +Array GLTFState::get_skeletons() { + return GLTFDocument::to_array(skeletons); +} + +void GLTFState::set_skeletons(Array p_skeletons) { + GLTFDocument::set_from_array(skeletons, p_skeletons); +} + +Dictionary GLTFState::get_skeleton_to_node() { + return GLTFDocument::to_dict(skeleton_to_node); +} + +void GLTFState::set_skeleton_to_node(Dictionary p_skeleton_to_node) { + GLTFDocument::set_from_dict(skeleton_to_node, p_skeleton_to_node); +} + +Array GLTFState::get_animations() { + return GLTFDocument::to_array(animations); +} + +void GLTFState::set_animations(Array p_animations) { + GLTFDocument::set_from_array(animations, p_animations); +} + +Node *GLTFState::get_scene_node(GLTFNodeIndex idx) { + if (!scene_nodes.has(idx)) { + return nullptr; + } + return scene_nodes[idx]; +} + +int GLTFState::get_animation_players_count(int idx) { + return animation_players.size(); +} + +AnimationPlayer *GLTFState::get_animation_player(int idx) { + ERR_FAIL_INDEX_V(idx, animation_players.size(), nullptr); + return animation_players[idx]; +} diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h new file mode 100644 index 0000000000..f21472ad1b --- /dev/null +++ b/modules/gltf/gltf_state.h @@ -0,0 +1,180 @@ +/*************************************************************************/ +/* gltf_state.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_STATE_H +#define GLTF_STATE_H + +#include "core/io/resource.h" +#include "core/templates/vector.h" +#include "editor_scene_importer_gltf.h" +#include "gltf_accessor.h" +#include "gltf_animation.h" +#include "gltf_buffer_view.h" +#include "gltf_camera.h" +#include "gltf_document.h" +#include "gltf_light.h" +#include "gltf_mesh.h" +#include "gltf_node.h" +#include "gltf_skeleton.h" +#include "gltf_skin.h" +#include "gltf_texture.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/texture.h" + +class GLTFState : public Resource { + GDCLASS(GLTFState, Resource); + friend class GLTFDocument; + friend class PackedSceneGLTF; + + Dictionary json; + int major_version = 0; + int minor_version = 0; + Vector<uint8_t> glb_data; + + bool use_named_skin_binds = false; + + Vector<Ref<GLTFNode>> nodes; + Vector<Vector<uint8_t>> buffers; + Vector<Ref<GLTFBufferView>> buffer_views; + Vector<Ref<GLTFAccessor>> accessors; + + Vector<Ref<GLTFMesh>> meshes; // meshes are loaded directly, no reason not to. + + Vector<AnimationPlayer *> animation_players; + Map<Ref<BaseMaterial3D>, GLTFMaterialIndex> material_cache; + Vector<Ref<BaseMaterial3D>> materials; + + String scene_name; + Vector<int> root_nodes; + Vector<Ref<GLTFTexture>> textures; + Vector<Ref<Texture2D>> images; + + Vector<Ref<GLTFSkin>> skins; + Vector<Ref<GLTFCamera>> cameras; + Vector<Ref<GLTFLight>> lights; + Set<String> unique_names; + + Vector<Ref<GLTFSkeleton>> skeletons; + Map<GLTFSkeletonIndex, GLTFNodeIndex> skeleton_to_node; + Vector<Ref<GLTFAnimation>> animations; + Map<GLTFNodeIndex, Node *> scene_nodes; + +protected: + static void _bind_methods(); + +public: + Dictionary get_json(); + void set_json(Dictionary p_json); + + int get_major_version(); + void set_major_version(int p_major_version); + + int get_minor_version(); + void set_minor_version(int p_minor_version); + + Vector<uint8_t> get_glb_data(); + void set_glb_data(Vector<uint8_t> p_glb_data); + + bool get_use_named_skin_binds(); + void set_use_named_skin_binds(bool p_use_named_skin_binds); + + Array get_nodes(); + void set_nodes(Array p_nodes); + + Array get_buffers(); + void set_buffers(Array p_buffers); + + Array get_buffer_views(); + void set_buffer_views(Array p_buffer_views); + + Array get_accessors(); + void set_accessors(Array p_accessors); + + Array get_meshes(); + void set_meshes(Array p_meshes); + + Array get_materials(); + void set_materials(Array p_materials); + + String get_scene_name(); + void set_scene_name(String p_scene_name); + + Array get_root_nodes(); + void set_root_nodes(Array p_root_nodes); + + Array get_textures(); + void set_textures(Array p_textures); + + Array get_images(); + void set_images(Array p_images); + + Array get_skins(); + void set_skins(Array p_skins); + + Array get_cameras(); + void set_cameras(Array p_cameras); + + Array get_lights(); + void set_lights(Array p_lights); + + Array get_unique_names(); + void set_unique_names(Array p_unique_names); + + Array get_skeletons(); + void set_skeletons(Array p_skeletons); + + Dictionary get_skeleton_to_node(); + void set_skeleton_to_node(Dictionary p_skeleton_to_node); + + Array get_animations(); + void set_animations(Array p_animations); + + Node *get_scene_node(GLTFNodeIndex idx); + + int get_animation_players_count(int idx); + + AnimationPlayer *get_animation_player(int idx); + + //void set_scene_nodes(Map<GLTFNodeIndex, Node *> p_scene_nodes) { + // this->scene_nodes = p_scene_nodes; + //} + + //void set_animation_players(Vector<AnimationPlayer *> p_animation_players) { + // this->animation_players = p_animation_players; + //} + + //Map<Ref<Material>, GLTFMaterialIndex> get_material_cache() { + // return this->material_cache; + //} + //void set_material_cache(Map<Ref<Material>, GLTFMaterialIndex> p_material_cache) { + // this->material_cache = p_material_cache; + //} +}; +#endif // GLTF_STATE_H diff --git a/modules/gltf/gltf_texture.cpp b/modules/gltf/gltf_texture.cpp new file mode 100644 index 0000000000..55434a5047 --- /dev/null +++ b/modules/gltf/gltf_texture.cpp @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* gltf_texture.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gltf_texture.h" + +void GLTFTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_src_image"), &GLTFTexture::get_src_image); + ClassDB::bind_method(D_METHOD("set_src_image", "src_image"), &GLTFTexture::set_src_image); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "src_image"), "set_src_image", "get_src_image"); // int +} + +GLTFImageIndex GLTFTexture::get_src_image() const { + return src_image; +} + +void GLTFTexture::set_src_image(GLTFImageIndex val) { + src_image = val; +} diff --git a/modules/gltf/gltf_texture.h b/modules/gltf/gltf_texture.h new file mode 100644 index 0000000000..5e0c9c307b --- /dev/null +++ b/modules/gltf/gltf_texture.h @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* gltf_texture.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GLTF_TEXTURE_H +#define GLTF_TEXTURE_H + +#include "core/io/resource.h" +#include "gltf_document.h" + +class GLTFTexture : public Resource { + GDCLASS(GLTFTexture, Resource); + +private: + GLTFImageIndex src_image; + +protected: + static void _bind_methods(); + +public: + GLTFImageIndex get_src_image() const; + void set_src_image(GLTFImageIndex val); +}; + +#endif // GLTF_TEXTURE_H diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp new file mode 100644 index 0000000000..bd5775af34 --- /dev/null +++ b/modules/gltf/register_types.cpp @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "register_types.h" + +#include "editor/editor_node.h" +#include "editor_scene_exporter_gltf_plugin.h" +#include "editor_scene_importer_gltf.h" +#include "gltf_accessor.h" +#include "gltf_animation.h" +#include "gltf_buffer_view.h" +#include "gltf_camera.h" +#include "gltf_document.h" +#include "gltf_light.h" +#include "gltf_mesh.h" +#include "gltf_node.h" +#include "gltf_skeleton.h" +#include "gltf_skin.h" +#include "gltf_spec_gloss.h" +#include "gltf_state.h" +#include "gltf_texture.h" + +#ifndef _3D_DISABLED +#ifdef TOOLS_ENABLED +static void _editor_init() { + Ref<EditorSceneImporterGLTF> import_gltf; + import_gltf.instance(); + ResourceImporterScene::get_singleton()->add_importer(import_gltf); +} +#endif +#endif + +void register_gltf_types() { +#ifndef _3D_DISABLED +#ifdef TOOLS_ENABLED + ClassDB::register_class<EditorSceneImporterGLTF>(); + ClassDB::APIType prev_api = ClassDB::get_current_api(); + ClassDB::set_current_api(ClassDB::API_EDITOR); + EditorPlugins::add_by_type<SceneExporterGLTFPlugin>(); + ClassDB::set_current_api(prev_api); + EditorNode::add_init_callback(_editor_init); +#endif + ClassDB::register_class<GLTFSpecGloss>(); + ClassDB::register_class<GLTFNode>(); + ClassDB::register_class<GLTFAnimation>(); + ClassDB::register_class<GLTFBufferView>(); + ClassDB::register_class<GLTFAccessor>(); + ClassDB::register_class<GLTFTexture>(); + ClassDB::register_class<GLTFSkeleton>(); + ClassDB::register_class<GLTFSkin>(); + ClassDB::register_class<GLTFMesh>(); + ClassDB::register_class<GLTFCamera>(); + ClassDB::register_class<GLTFLight>(); + ClassDB::register_class<GLTFState>(); + ClassDB::register_class<GLTFDocument>(); + ClassDB::register_class<PackedSceneGLTF>(); +#endif +} + +void unregister_gltf_types() { +} diff --git a/modules/gltf/register_types.h b/modules/gltf/register_types.h new file mode 100644 index 0000000000..ffbc586ade --- /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-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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(); diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index cda217acf0..55cc0bfb9d 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -724,7 +724,7 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In return true; } else { selected_palette = -1; - mesh_library_palette->unselect_all(); + mesh_library_palette->deselect_all(); update_palette(); _update_cursor_instance(); return true; @@ -1288,7 +1288,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { info_message->set_align(Label::ALIGN_CENTER); info_message->set_autowrap(true); info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); + info_message->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); mesh_library_palette->add_child(info_message); edit_axis = Vector3::AXIS_Y; diff --git a/modules/jpg/SCsub b/modules/jpg/SCsub index 8ee8e6dd6e..7c6ceeea29 100644 --- a/modules/jpg/SCsub +++ b/modules/jpg/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_jpg = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + # Not unbundled for now as they are not commonly available as shared library thirdparty_dir = "#thirdparty/jpeg-compressor/" thirdparty_sources = [ @@ -17,7 +20,15 @@ env_jpg.Prepend(CPPPATH=[thirdparty_dir]) env_thirdparty = env_jpg.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot source files + +module_obj = [] + +env_jpg.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -# Godot's own source files -env_jpg.add_source_files(env.modules_sources, "*.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp index 320da182f8..d1d8e18583 100644 --- a/modules/jsonrpc/jsonrpc.cpp +++ b/modules/jsonrpc/jsonrpc.cpp @@ -151,7 +151,7 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem } String JSONRPC::process_string(const String &p_input) { - if (p_input.empty()) { + if (p_input.is_empty()) { return String(); } diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 49ae6e28b2..566fdbc791 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -40,8 +40,8 @@ //#define DEBUG_TEXTURES void LightmapperRD::add_mesh(const MeshData &p_mesh) { - ERR_FAIL_COND(p_mesh.albedo_on_uv2.is_null() || p_mesh.albedo_on_uv2->empty()); - ERR_FAIL_COND(p_mesh.emission_on_uv2.is_null() || p_mesh.emission_on_uv2->empty()); + ERR_FAIL_COND(p_mesh.albedo_on_uv2.is_null() || p_mesh.albedo_on_uv2->is_empty()); + ERR_FAIL_COND(p_mesh.emission_on_uv2.is_null() || p_mesh.emission_on_uv2->is_empty()); ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_width() != p_mesh.emission_on_uv2->get_width()); ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_height() != p_mesh.emission_on_uv2->get_height()); ERR_FAIL_COND(p_mesh.points.size() == 0); @@ -447,7 +447,6 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i if (cell != last_cell) { //cell changed, update pointer to indices giw[cell * 2 + 1] = i; - last_cell = cell; solidw[cell] = true; } tiw[i] = triangle_sort[i].triangle_index; diff --git a/modules/mbedtls/SCsub b/modules/mbedtls/SCsub index 3b1739c6ee..4fcbe8fb43 100755 --- a/modules/mbedtls/SCsub +++ b/modules/mbedtls/SCsub @@ -5,8 +5,11 @@ Import("env_modules") env_mbed_tls = env_modules.Clone() +# Thirdparty source files + +thirdparty_obj = [] + if env["builtin_mbedtls"]: - # Thirdparty source files thirdparty_sources = [ "aes.c", "aesni.c", @@ -96,11 +99,21 @@ if env["builtin_mbedtls"]: env_thirdparty = env_mbed_tls.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + + +# Godot source files -# Module sources -env_mbed_tls.add_source_files(env.modules_sources, "*.cpp") +module_obj = [] + +env_mbed_tls.add_source_files(module_obj, "*.cpp") if env["tests"]: env_mbed_tls.Append(CPPDEFINES=["TESTS_ENABLED"]) - env_mbed_tls.add_source_files(env.modules_sources, "./tests/*.cpp") + env_mbed_tls.add_source_files(module_obj, "./tests/*.cpp") + +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index 4ea38ebd60..56bdd20e6b 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -205,7 +205,7 @@ Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByte ERR_FAIL_COND_V_MSG(ctx != nullptr, ERR_FILE_ALREADY_IN_USE, "HMACContext already started."); // HMAC keys can be any size. - ERR_FAIL_COND_V_MSG(p_key.empty(), ERR_INVALID_PARAMETER, "Key must not be empty."); + ERR_FAIL_COND_V_MSG(p_key.is_empty(), ERR_INVALID_PARAMETER, "Key must not be empty."); hash_type = p_hash_type; mbedtls_md_type_t ht = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, hash_len); @@ -224,7 +224,7 @@ Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByte Error HMACContextMbedTLS::update(PackedByteArray p_data) { ERR_FAIL_COND_V_MSG(ctx == nullptr, ERR_INVALID_DATA, "Start must be called before update."); - ERR_FAIL_COND_V_MSG(p_data.empty(), ERR_INVALID_PARAMETER, "Src must not be empty."); + ERR_FAIL_COND_V_MSG(p_data.is_empty(), ERR_INVALID_PARAMETER, "Src must not be empty."); int ret = mbedtls_md_hmac_update((mbedtls_md_context_t *)ctx, (const uint8_t *)p_data.ptr(), (size_t)p_data.size()); return ret ? FAILED : OK; diff --git a/modules/meshoptimizer/SCsub b/modules/meshoptimizer/SCsub new file mode 100644 index 0000000000..3f86bb4f00 --- /dev/null +++ b/modules/meshoptimizer/SCsub @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_meshoptimizer = env_modules.Clone() + +# Thirdparty source files + +thirdparty_obj = [] + +thirdparty_dir = "#thirdparty/meshoptimizer/" +thirdparty_sources = [ + "allocator.cpp", + "clusterizer.cpp", + "indexcodec.cpp", + "indexgenerator.cpp", + "overdrawanalyzer.cpp", + "overdrawoptimizer.cpp", + "simplifier.cpp", + "spatialorder.cpp", + "stripifier.cpp", + "vcacheanalyzer.cpp", + "vcacheoptimizer.cpp", + "vertexcodec.cpp", + "vertexfilter.cpp", + "vfetchanalyzer.cpp", + "vfetchoptimizer.cpp", +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +env_thirdparty = env_meshoptimizer.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot source files + +module_obj = [] + +env_meshoptimizer.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/meshoptimizer/config.py b/modules/meshoptimizer/config.py new file mode 100644 index 0000000000..82e4e43397 --- /dev/null +++ b/modules/meshoptimizer/config.py @@ -0,0 +1,7 @@ +def can_build(env, platform): + # Having this on release by default, it's small and a lot of users like to do procedural stuff + return True + + +def configure(env): + pass diff --git a/modules/meshoptimizer/register_types.cpp b/modules/meshoptimizer/register_types.cpp new file mode 100644 index 0000000000..26c8c6ab72 --- /dev/null +++ b/modules/meshoptimizer/register_types.cpp @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "register_types.h" +#include "scene/resources/surface_tool.h" +#include "thirdparty/meshoptimizer/meshoptimizer.h" + +void register_meshoptimizer_types() { + SurfaceTool::optimize_vertex_cache_func = meshopt_optimizeVertexCache; + SurfaceTool::simplify_func = meshopt_simplify; +} + +void unregister_meshoptimizer_types() { + SurfaceTool::optimize_vertex_cache_func = nullptr; + SurfaceTool::simplify_func = nullptr; +} diff --git a/modules/meshoptimizer/register_types.h b/modules/meshoptimizer/register_types.h new file mode 100644 index 0000000000..42b3a76a85 --- /dev/null +++ b/modules/meshoptimizer/register_types.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 MESHOPTIMIZER_REGISTER_TYPES_H +#define MESHOPTIMIZER_REGISTER_TYPES_H + +void register_meshoptimizer_types(); +void unregister_meshoptimizer_types(); + +#endif // PVR_REGISTER_TYPES_H diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 9b10bac2f0..309abfbff7 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -263,7 +263,8 @@ def configure(env, env_mono): env_mono.Append(CPPDEFINES=["_REENTRANT"]) if mono_static: - env.Append(LINKFLAGS=["-rdynamic"]) + if not is_javascript: + env.Append(LINKFLAGS=["-rdynamic"]) mono_lib_file = os.path.join(mono_lib_path, "lib" + mono_lib + ".a") diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp index e3119a8da7..5f7e0b2308 100644 --- a/modules/mono/class_db_api_json.cpp +++ b/modules/mono/class_db_api_json.cpp @@ -71,7 +71,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { while ((k = t->method_map.next(k))) { String name = k->operator String(); - ERR_CONTINUE(name.empty()); + ERR_CONTINUE(name.is_empty()); if (name[0] == '_') { continue; // Ignore non-virtual methods that start with an underscore @@ -122,7 +122,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { method_dict["hint_flags"] = mb->get_hint_flags(); } - if (!methods.empty()) { + if (!methods.is_empty()) { class_dict["methods"] = methods; } } @@ -149,7 +149,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { constant_dict["value"] = t->constant_map[F->get()]; } - if (!constants.empty()) { + if (!constants.is_empty()) { class_dict["constants"] = constants; } } @@ -184,7 +184,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { } } - if (!signals.empty()) { + if (!signals.is_empty()) { class_dict["signals"] = signals; } } @@ -214,7 +214,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { property_dict["getter"] = psg->getter; } - if (!properties.empty()) { + if (!properties.is_empty()) { class_dict["property_setget"] = properties; } } @@ -233,7 +233,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { property_dict["usage"] = F->get().usage; } - if (!property_list.empty()) { + if (!property_list.is_empty()) { class_dict["property_list"] = property_list; } } diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 63ac0956f4..381e45f5a3 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -346,14 +346,18 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin "// }\n" "}\n"; - String base_class_name = get_base_class_name(p_base_class_name, p_class_name); + // Replaces all spaces in p_class_name with underscores to prevent + // erronous C# Script templates from being generated when the object name + // has spaces in it. + String class_name_no_spaces = p_class_name.replace(" ", "_"); + String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces); script_template = script_template.replace("%BASE%", base_class_name) - .replace("%CLASS%", p_class_name); + .replace("%CLASS%", class_name_no_spaces); Ref<CSharpScript> script; script.instance(); script->set_source_code(script_template); - script->set_name(p_class_name); + script->set_name(class_name_no_spaces); return script; } @@ -364,9 +368,10 @@ bool CSharpLanguage::is_using_templates() { void CSharpLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) { String src = p_script->get_source_code(); - String base_class_name = get_base_class_name(p_base_class_name, p_class_name); + String class_name_no_spaces = p_class_name.replace(" ", "_"); + String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces); src = src.replace("%BASE%", base_class_name) - .replace("%CLASS%", p_class_name) + .replace("%CLASS%", class_name_no_spaces) .replace("%TS%", _get_indentation()); p_script->set_source_code(src); } @@ -395,7 +400,7 @@ bool CSharpLanguage::supports_builtin_mode() const { #ifdef TOOLS_ENABLED static String variant_type_to_managed_name(const String &p_var_type_name) { - if (p_var_type_name.empty()) { + if (p_var_type_name.is_empty()) { return "object"; } @@ -757,7 +762,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() { String appname = ProjectSettings::get_singleton()->get("application/config/name"); String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.empty()) { + if (appname_safe.is_empty()) { appname_safe = "UnnamedProject"; } @@ -854,7 +859,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { to_reload.push_back(script); - if (script->get_path().empty()) { + if (script->get_path().is_empty()) { script->tied_class_name_for_reload = script->script_class->get_name_for_lookup(); script->tied_class_namespace_for_reload = script->script_class->get_namespace(); } @@ -971,7 +976,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #endif script->signals_invalidated = true; - if (!script->get_path().empty()) { + if (!script->get_path().is_empty()) { script->reload(p_soft_reload); if (!script->valid) { @@ -1441,7 +1446,7 @@ Map<Object *, CSharpScriptBinding>::Element *CSharpLanguage::insert_script_bindi void CSharpLanguage::free_instance_binding_data(void *p_data) { if (GDMono::get_singleton() == nullptr) { #ifdef DEBUG_ENABLED - CRASH_COND(!script_bindings.empty()); + CRASH_COND(!script_bindings.is_empty()); #endif // Mono runtime finalized, all the gchandle bindings were already released return; @@ -3123,7 +3128,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg ERR_FAIL_COND_V_MSG(p_argcount == 0, nullptr, "Cannot create script instance. The class '" + script_class->get_full_name() + "' does not define a parameterless constructor." + - (get_path().empty() ? String() : " Path: '" + get_path() + "'.")); + (get_path().is_empty() ? String() : " Path: '" + get_path() + "'.")); ERR_FAIL_V_MSG(nullptr, "Constructor not found."); } @@ -3278,7 +3283,7 @@ bool CSharpScript::instance_has(const Object *p_this) const { } bool CSharpScript::has_source_code() const { - return !source.empty(); + return !source.is_empty(); } String CSharpScript::get_source_code() const { @@ -3593,7 +3598,7 @@ Error CSharpScript::load_source_code(const String &p_path) { void CSharpScript::_update_name() { String path = get_path(); - if (!path.empty()) { + if (!path.is_empty()) { name = get_path().get_file().get_basename(); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index e18ed7f107..e9bb701562 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -173,6 +173,8 @@ namespace GodotTools.Export assemblies[projectDllName] = projectDllSrcPath; + string bclDir = DeterminePlatformBclDir(platform); + if (platform == OS.Platforms.Android) { string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext"); @@ -183,8 +185,49 @@ namespace GodotTools.Export assemblies["Mono.Android"] = monoAndroidAssemblyPath; } + else if (platform == OS.Platforms.HTML5) + { + // Ideally these would be added automatically since they're referenced by the wasm BCL assemblies. + // However, at least in the case of 'WebAssembly.Net.Http' for some reason the BCL assemblies + // reference a different version even though the assembly is the same, for some weird reason. - string bclDir = DeterminePlatformBclDir(platform); + var wasmFrameworkAssemblies = new[] {"WebAssembly.Bindings", "WebAssembly.Net.WebSockets"}; + + foreach (string thisWasmFrameworkAssemblyName in wasmFrameworkAssemblies) + { + string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName + ".dll"); + if (!File.Exists(thisWasmFrameworkAssemblyPath)) + throw new FileNotFoundException($"Assembly not found: '{thisWasmFrameworkAssemblyName}'", thisWasmFrameworkAssemblyPath); + assemblies[thisWasmFrameworkAssemblyName] = thisWasmFrameworkAssemblyPath; + } + + // Assemblies that can have a different name in a newer version. Newer version must come first and it has priority. + (string newName, string oldName)[] wasmFrameworkAssembliesOneOf = new[] + { + ("System.Net.Http.WebAssemblyHttpHandler", "WebAssembly.Net.Http") + }; + + foreach (var thisWasmFrameworkAssemblyName in wasmFrameworkAssembliesOneOf) + { + string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.newName + ".dll"); + if (File.Exists(thisWasmFrameworkAssemblyPath)) + { + assemblies[thisWasmFrameworkAssemblyName.newName] = thisWasmFrameworkAssemblyPath; + } + else + { + thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.oldName + ".dll"); + if (!File.Exists(thisWasmFrameworkAssemblyPath)) + { + throw new FileNotFoundException("Expected one of the following assemblies but none were found: " + + $"'{thisWasmFrameworkAssemblyName.newName}' / '{thisWasmFrameworkAssemblyName.oldName}'", + thisWasmFrameworkAssemblyPath); + } + + assemblies[thisWasmFrameworkAssemblyName.oldName] = thisWasmFrameworkAssemblyPath; + } + } + } var initialAssemblies = assemblies.Duplicate(); internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 968f9f29c7..32283d814e 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -97,7 +97,7 @@ #define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL "::signal_info_to_callable" #define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL "::callable_to_signal_info" -#define BINDINGS_GENERATOR_VERSION UINT32_C(12) +#define BINDINGS_GENERATOR_VERSION UINT32_C(13) const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n"); @@ -181,7 +181,7 @@ static String snake_to_camel_case(const String &p_identifier, bool p_input_is_up String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) { // Based on the version in EditorHelp - if (p_bbcode.empty()) { + if (p_bbcode.is_empty()) { return String(); } @@ -644,7 +644,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) { - CRASH_COND(p_ienum.constants.empty()); + CRASH_COND(p_ienum.constants.is_empty()); const ConstantInterface &front_iconstant = p_ienum.constants.front()->get(); Vector<String> front_parts = front_iconstant.name.split("_", /* p_allow_empty: */ true); @@ -819,7 +819,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append(";"); } - if (!global_constants.empty()) { + if (!global_constants.is_empty()) { p_output.append("\n"); } @@ -830,7 +830,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { for (List<EnumInterface>::Element *E = global_enums.front(); E; E = E->next()) { const EnumInterface &ienum = E->get(); - CRASH_COND(ienum.constants.empty()); + CRASH_COND(ienum.constants.is_empty()); String enum_proxy_name = ienum.cname.operator String(); @@ -1283,7 +1283,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str for (const List<EnumInterface>::Element *E = itype.enums.front(); E; E = E->next()) { const EnumInterface &ienum = E->get(); - ERR_FAIL_COND_V(ienum.constants.empty(), ERR_BUG); + ERR_FAIL_COND_V(ienum.constants.is_empty(), ERR_BUG); output.append(MEMBER_BEGIN "public enum "); output.append(ienum.cname.operator String()); @@ -1661,14 +1661,14 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf cs_in_statements += def_arg; cs_in_statements += ";\n" INDENT3; - icall_params += arg_type->cs_in.empty() ? arg_in : sformat(arg_type->cs_in, arg_in); + icall_params += arg_type->cs_in.is_empty() ? arg_in : sformat(arg_type->cs_in, arg_in); // Apparently the name attribute must not include the @ String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1, iarg.name.length()) : iarg.name; default_args_doc.append(MEMBER_BEGIN "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is " + def_arg + "</param>"); } else { - icall_params += arg_type->cs_in.empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name); + icall_params += arg_type->cs_in.is_empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name); } } @@ -1714,7 +1714,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf } if (p_imethod.is_deprecated) { - if (p_imethod.deprecation_message.empty()) { + if (p_imethod.deprecation_message.is_empty()) { WARN_PRINT("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'."); } @@ -1782,7 +1782,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (return_type->cname == name_cache.type_void) { p_output.append(im_call + "(" + icall_params + ");\n"); - } else if (return_type->cs_out.empty()) { + } else if (return_type->cs_out.is_empty()) { p_output.append("return " + im_call + "(" + icall_params + ");\n"); } else { p_output.append(sformat(return_type->cs_out, im_call, icall_params, return_type->cs_type, return_type->im_type_out)); @@ -1839,7 +1839,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf } if (p_isignal.is_deprecated) { - if (p_isignal.deprecation_message.empty()) { + if (p_isignal.deprecation_message.is_empty()) { WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); } @@ -1999,12 +1999,12 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { #define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ { \ - output.append("\tmono_add_internal_call("); \ + output.append("\tGDMonoUtils::add_internal_call("); \ output.append("\"" BINDINGS_NAMESPACE "."); \ output.append(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ output.append("::"); \ output.append(m_icall.name); \ - output.append("\", (void*)"); \ + output.append("\", "); \ output.append(m_icall.name); \ output.append(");\n"); \ } @@ -2263,7 +2263,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte } if (!ret_void) { - if (return_type->c_out.empty()) { + if (return_type->c_out.is_empty()) { p_output.append("\treturn " C_LOCAL_RET ";\n"); } else if (return_type->ret_as_byref_arg) { p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name, "arg_ret")); @@ -2585,7 +2585,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { int argc = method_info.arguments.size(); - if (method_info.name.empty()) { + if (method_info.name.is_empty()) { continue; } @@ -3100,44 +3100,11 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { INSERT_INT_TYPE("sbyte", int8_t, int64_t); INSERT_INT_TYPE("short", int16_t, int64_t); INSERT_INT_TYPE("int", int32_t, int64_t); + INSERT_INT_TYPE("long", int64_t, int64_t); INSERT_INT_TYPE("byte", uint8_t, int64_t); INSERT_INT_TYPE("ushort", uint16_t, int64_t); INSERT_INT_TYPE("uint", uint32_t, int64_t); - - itype = TypeInterface::create_value_type(String("long")); - { - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; - itype.c_out = "\t*%3 = (%0)%1;\n"; - itype.c_type = "int64_t"; - itype.c_arg_in = "&%s_in"; - } - itype.c_type_in = "int64_t*"; - itype.c_type_out = "int64_t"; - itype.im_type_in = "ref " + itype.name; - itype.im_type_out = "out " + itype.name; - itype.cs_in = "ref %0"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.ret_as_byref_arg = true; - builtin_types.insert(itype.cname, itype); - - itype = TypeInterface::create_value_type(String("ulong")); - { - itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; - itype.c_out = "\t*%3 = (%0)%1;\n"; - itype.c_type = "int64_t"; - itype.c_arg_in = "&%s_in"; - } - itype.c_type_in = "uint64_t*"; - itype.c_type_out = "uint64_t"; - itype.im_type_in = "ref " + itype.name; - itype.im_type_out = "out " + itype.name; - itype.cs_in = "ref %0"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.ret_as_byref_arg = true; - builtin_types.insert(itype.cname, itype); + INSERT_INT_TYPE("ulong", uint64_t, int64_t); } // Floating point types @@ -3149,20 +3116,16 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.proxy_name = "float"; { // The expected type for 'float' in ptrcall is 'double' - itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; - itype.c_out = "\t*%3 = (%0)%1;\n"; + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; itype.c_type = "double"; - itype.c_type_in = "float*"; + itype.c_type_in = "float"; itype.c_type_out = "float"; itype.c_arg_in = "&%s_in"; } itype.cs_type = itype.proxy_name; - itype.im_type_in = "ref " + itype.proxy_name; - itype.im_type_out = "out " + itype.proxy_name; - itype.cs_in = "ref %0"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.ret_as_byref_arg = true; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; builtin_types.insert(itype.cname, itype); // double @@ -3171,20 +3134,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.cname = itype.name; itype.proxy_name = "double"; { - itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; - itype.c_out = "\t*%3 = (%0)%1;\n"; itype.c_type = "double"; - itype.c_type_in = "double*"; + itype.c_type_in = "double"; itype.c_type_out = "double"; - itype.c_arg_in = "&%s_in"; + itype.c_arg_in = "&%s"; } itype.cs_type = itype.proxy_name; - itype.im_type_in = "ref " + itype.proxy_name; - itype.im_type_out = "out " + itype.proxy_name; - itype.cs_in = "ref %0"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.ret_as_byref_arg = true; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; builtin_types.insert(itype.cname, itype); } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 6fefcd48a4..0cc1bb5f46 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -45,7 +45,7 @@ class BindingsGenerator { struct ConstantInterface { String name; String proxy_name; - int value; + int value = 0; const DocData::ConstantDoc *const_doc; ConstantInterface() {} @@ -75,7 +75,7 @@ class BindingsGenerator { struct PropertyInterface { StringName cname; String proxy_name; - int index; + int index = 0; StringName setter; StringName getter; @@ -480,7 +480,7 @@ class BindingsGenerator { String im_type_out; // Return type for the C# method declaration. Also used as companion of [unique_siq] String im_sig; // Signature for the C# method declaration String unique_sig; // Unique signature to avoid duplicates in containers - bool editor_only; + bool editor_only = false; InternalCall() {} diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp index 2d37b1306c..a7ab87aaaf 100644 --- a/modules/mono/editor/code_completion.cpp +++ b/modules/mono/editor/code_completion.cpp @@ -150,7 +150,7 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr List<String> directories; directories.push_back(dir_access->get_current_dir()); - while (!directories.empty()) { + while (!directories.is_empty()) { dir_access->change_dir(directories.back()->get()); directories.pop_back(); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 68fc372959..21a2c14e3d 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -47,7 +47,6 @@ #include "../godotsharp_dirs.h" #include "../mono_gd/gd_mono_marshal.h" #include "../utils/osx_utils.h" -#include "bindings_generator.h" #include "code_completion.h" #include "godotsharp_export.h" #include "script_class_parser.h" @@ -173,35 +172,6 @@ MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_st return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh); } -BindingsGenerator *godot_icall_BindingsGenerator_Ctor() { - return memnew(BindingsGenerator); -} - -void godot_icall_BindingsGenerator_Dtor(BindingsGenerator *p_handle) { - memdelete(p_handle); -} - -MonoBoolean godot_icall_BindingsGenerator_LogPrintEnabled(BindingsGenerator *p_handle) { - return p_handle->is_log_print_enabled(); -} - -void godot_icall_BindingsGenerator_SetLogPrintEnabled(BindingsGenerator p_handle, MonoBoolean p_enabled) { - p_handle.set_log_print_enabled(p_enabled); -} - -int32_t godot_icall_BindingsGenerator_GenerateCsApi(BindingsGenerator *p_handle, MonoString *p_output_dir) { - String output_dir = GDMonoMarshal::mono_string_to_godot(p_output_dir); - return p_handle->generate_cs_api(output_dir); -} - -uint32_t godot_icall_BindingsGenerator_Version() { - return BindingsGenerator::get_version(); -} - -uint32_t godot_icall_BindingsGenerator_CsGlueVersion() { - return CS_GLUE_VERSION; -} - int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) { *r_error_str = nullptr; @@ -225,7 +195,7 @@ int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObje } } else { String error_str = scp.get_error(); - if (!error_str.empty()) { + if (!error_str.is_empty()) { *r_error_str = GDMonoMarshal::mono_string_from_godot(error_str); } } @@ -400,75 +370,66 @@ MonoBoolean godot_icall_Utils_OS_UnixFileHasExecutableAccess(MonoString *p_file_ void register_editor_internal_calls() { // GodotSharpDirs - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", (void *)godot_icall_GodotSharpDirs_ResDataDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", (void *)godot_icall_GodotSharpDirs_ResMetadataDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesBaseDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", (void *)godot_icall_GodotSharpDirs_ResConfigDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", (void *)godot_icall_GodotSharpDirs_ResTempDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", (void *)godot_icall_GodotSharpDirs_MonoUserDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", (void *)godot_icall_GodotSharpDirs_MonoLogsDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", (void *)godot_icall_GodotSharpDirs_MonoSolutionsDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", (void *)godot_icall_GodotSharpDirs_BuildLogsDirs); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", (void *)godot_icall_GodotSharpDirs_ProjectSlnPath); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", (void *)godot_icall_GodotSharpDirs_ProjectCsProjPath); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", (void *)godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", (void *)godot_icall_GodotSharpDirs_DataMonoEtcDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", (void *)godot_icall_GodotSharpDirs_DataMonoLibDir); - mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", (void *)godot_icall_GodotSharpDirs_DataMonoBinDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", godot_icall_GodotSharpDirs_ResDataDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", godot_icall_GodotSharpDirs_ResMetadataDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", godot_icall_GodotSharpDirs_ResAssembliesBaseDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", godot_icall_GodotSharpDirs_ResAssembliesDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", godot_icall_GodotSharpDirs_ResConfigDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", godot_icall_GodotSharpDirs_ResTempDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", godot_icall_GodotSharpDirs_ResTempAssembliesDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", godot_icall_GodotSharpDirs_MonoUserDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", godot_icall_GodotSharpDirs_MonoLogsDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", godot_icall_GodotSharpDirs_MonoSolutionsDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", godot_icall_GodotSharpDirs_BuildLogsDirs); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", godot_icall_GodotSharpDirs_ProjectSlnPath); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", godot_icall_GodotSharpDirs_ProjectCsProjPath); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", godot_icall_GodotSharpDirs_DataEditorToolsDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", godot_icall_GodotSharpDirs_DataMonoEtcDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", godot_icall_GodotSharpDirs_DataMonoLibDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", godot_icall_GodotSharpDirs_DataMonoBinDir); // EditorProgress - mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", (void *)godot_icall_EditorProgress_Create); - mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", (void *)godot_icall_EditorProgress_Dispose); - mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", (void *)godot_icall_EditorProgress_Step); - - // BiningsGenerator - mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Ctor", (void *)godot_icall_BindingsGenerator_Ctor); - mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Dtor", (void *)godot_icall_BindingsGenerator_Dtor); - mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_LogPrintEnabled", (void *)godot_icall_BindingsGenerator_LogPrintEnabled); - mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_SetLogPrintEnabled", (void *)godot_icall_BindingsGenerator_SetLogPrintEnabled); - mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_GenerateCsApi", (void *)godot_icall_BindingsGenerator_GenerateCsApi); - mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Version", (void *)godot_icall_BindingsGenerator_Version); - mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_CsGlueVersion", (void *)godot_icall_BindingsGenerator_CsGlueVersion); + GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", godot_icall_EditorProgress_Create); + GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose); + GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step); // ScriptClassParser - mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile); + GDMonoUtils::add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", godot_icall_ScriptClassParser_ParseFile); // ExportPlugin - mono_add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", (void *)godot_icall_ExportPlugin_GetExportedAssemblyDependencies); + GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies); // Internals - mono_add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", (void *)godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); - mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir); - mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath); - mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled); - mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits); - mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble); - mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration); - mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash); - mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash); - mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded); - mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies); - mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", (void *)godot_icall_Internal_EditorDebuggerNodeReloadScripts); - mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit); - mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); - mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); - mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot); - mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay); - mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop); - mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); - mono_add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", (void *)godot_icall_Internal_CodeCompletionRequest); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", godot_icall_Internal_FullTemplatesDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", godot_icall_Internal_SimplifyGodotPath); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", godot_icall_Internal_IsOsxAppBundleInstalled); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", godot_icall_Internal_GodotIs32Bits); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", godot_icall_Internal_GodotIsRealTDouble); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", godot_icall_Internal_GodotMainIteration); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", godot_icall_Internal_GetCoreApiHash); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", godot_icall_Internal_GetEditorApiHash); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", godot_icall_Internal_IsAssembliesReloadingNeeded); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", godot_icall_Internal_ReloadAssemblies); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", godot_icall_Internal_GetScriptsMetadataOrNothing); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", godot_icall_Internal_CodeCompletionRequest); // Globals - mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale); - mono_add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", (void *)godot_icall_Globals_GlobalDef); - mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", (void *)godot_icall_Globals_EditorDef); - mono_add_internal_call("GodotTools.Internals.Globals::internal_TTR", (void *)godot_icall_Globals_TTR); + GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", godot_icall_Globals_EditorScale); + GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", godot_icall_Globals_GlobalDef); + GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", godot_icall_Globals_EditorDef); + GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_TTR", godot_icall_Globals_TTR); // Utils.OS - mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName); - mono_add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", (void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess); + GDMonoUtils::add_internal_call("GodotTools.Utils.OS::GetPlatformName", godot_icall_Utils_OS_GetPlatformName); + GDMonoUtils::add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", godot_icall_Utils_OS_UnixFileHasExecutableAccess); } diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 1a0d5743ae..b006eed69f 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -55,10 +55,10 @@ MonoAssemblyName *new_mono_assembly_name() { struct AssemblyRefInfo { String name; - uint16_t major; - uint16_t minor; - uint16_t build; - uint16_t revision; + uint16_t major = 0; + uint16_t minor = 0; + uint16_t build = 0; + uint16_t revision = 0; }; AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) { diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h index 3c55fa07a7..deb6061191 100644 --- a/modules/mono/editor/script_class_parser.h +++ b/modules/mono/editor/script_class_parser.h @@ -45,22 +45,22 @@ public: }; String name; - Type type; + Type type = NAMESPACE_DECL; }; struct ClassDecl { String name; String namespace_; Vector<String> base; - bool nested; + bool nested = false; }; private: String code; - int idx; - int line; + int idx = 0; + int line = 0; String error_str; - bool error; + bool error = false; Variant value; Vector<ClassDecl> classes; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index f7703c77cc..868c3536fe 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -52,7 +52,7 @@ namespace Godot } /// <summary> - /// The area of this rect. + /// The area of this Rect2. /// </summary> /// <value>Equivalent to <see cref="GetArea()"/>.</value> public real_t Area @@ -64,7 +64,7 @@ namespace Godot /// Returns a Rect2 with equivalent position and size, modified so that /// the top-left corner is the origin and width and height are positive. /// </summary> - /// <returns>The modified rect.</returns> + /// <returns>The modified Rect2.</returns> public Rect2 Abs() { Vector2 end = End; @@ -74,10 +74,11 @@ namespace Godot /// <summary> /// Returns the intersection of this Rect2 and `b`. + /// If the rectangles do not intersect, an empty Rect2 is returned. /// </summary> - /// <param name="b">The other rect.</param> - /// <returns>The clipped rect.</returns> - public Rect2 Clip(Rect2 b) + /// <param name="b">The other Rect2.</param> + /// <returns>The intersection of this Rect2 and `b`, or an empty Rect2 if they do not intersect.</returns> + public Rect2 Intersection(Rect2 b) { var newRect = b; @@ -101,8 +102,8 @@ namespace Godot /// <summary> /// Returns true if this Rect2 completely encloses another one. /// </summary> - /// <param name="b">The other rect that may be enclosed.</param> - /// <returns>A bool for whether or not this rect encloses `b`.</returns> + /// <param name="b">The other Rect2 that may be enclosed.</param> + /// <returns>A bool for whether or not this Rect2 encloses `b`.</returns> public bool Encloses(Rect2 b) { return b._position.x >= _position.x && b._position.y >= _position.y && @@ -114,7 +115,7 @@ namespace Godot /// Returns this Rect2 expanded to include a given point. /// </summary> /// <param name="to">The point to include.</param> - /// <returns>The expanded rect.</returns> + /// <returns>The expanded Rect2.</returns> public Rect2 Expand(Vector2 to) { var expanded = this; @@ -156,10 +157,10 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown a given amount of units towards all the sides. + /// Returns a copy of the Rect2 grown by the specified amount on all sides. /// </summary> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown rect.</returns> + /// <returns>The grown Rect2.</returns> public Rect2 Grow(real_t by) { var g = this; @@ -173,13 +174,13 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown a given amount of units towards each direction individually. + /// Returns a copy of the Rect2 grown by the specified amount on each side individually. /// </summary> - /// <param name="left">The amount to grow by on the left.</param> - /// <param name="top">The amount to grow by on the top.</param> - /// <param name="right">The amount to grow by on the right.</param> - /// <param name="bottom">The amount to grow by on the bottom.</param> - /// <returns>The grown rect.</returns> + /// <param name="left">The amount to grow by on the left side.</param> + /// <param name="top">The amount to grow by on the top side.</param> + /// <param name="right">The amount to grow by on the right side.</param> + /// <param name="bottom">The amount to grow by on the bottom side.</param> + /// <returns>The grown Rect2.</returns> public Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom) { var g = this; @@ -193,19 +194,19 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown a given amount of units towards the <see cref="Margin"/> direction. + /// Returns a copy of the Rect2 grown by the specified amount on the specified Side. /// </summary> - /// <param name="margin">The direction to grow in.</param> + /// <param name="side">The side to grow.</param> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown rect.</returns> - public Rect2 GrowMargin(Margin margin, real_t by) + /// <returns>The grown Rect2.</returns> + public Rect2 GrowSide(Side side, real_t by) { var g = this; - g = g.GrowIndividual(Margin.Left == margin ? by : 0, - Margin.Top == margin ? by : 0, - Margin.Right == margin ? by : 0, - Margin.Bottom == margin ? by : 0); + g = g.GrowIndividual(Side.Left == side ? by : 0, + Side.Top == side ? by : 0, + Side.Right == side ? by : 0, + Side.Bottom == side ? by : 0); return g; } @@ -213,7 +214,7 @@ namespace Godot /// <summary> /// Returns true if the Rect2 is flat or empty, or false otherwise. /// </summary> - /// <returns>A bool for whether or not the rect has area.</returns> + /// <returns>A bool for whether or not the Rect2 has area.</returns> public bool HasNoArea() { return _size.x <= 0 || _size.y <= 0; @@ -223,7 +224,7 @@ namespace Godot /// Returns true if the Rect2 contains a point, or false otherwise. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the rect contains `point`.</returns> + /// <returns>A bool for whether or not the Rect2 contains `point`.</returns> public bool HasPoint(Vector2 point) { if (point.x < _position.x) @@ -246,7 +247,7 @@ namespace Godot /// If `includeBorders` is true, they will also be considered overlapping /// if their borders touch, even without intersection. /// </summary> - /// <param name="b">The other rect to check for intersections with.</param> + /// <param name="b">The other Rect2 to check for intersections with.</param> /// <param name="includeBorders">Whether or not to consider borders.</param> /// <returns>A bool for whether or not they are intersecting.</returns> public bool Intersects(Rect2 b, bool includeBorders = false) @@ -296,8 +297,8 @@ namespace Godot /// <summary> /// Returns a larger Rect2 that contains this Rect2 and `b`. /// </summary> - /// <param name="b">The other rect.</param> - /// <returns>The merged rect.</returns> + /// <param name="b">The other Rect2.</param> + /// <returns>The merged Rect2.</returns> public Rect2 Merge(Rect2 b) { Rect2 newRect; @@ -387,11 +388,11 @@ namespace Godot } /// <summary> - /// Returns true if this rect and `other` are approximately equal, by running + /// Returns true if this Rect2 and `other` are approximately equal, by running /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. /// </summary> - /// <param name="other">The other rect to compare.</param> - /// <returns>Whether or not the rects are approximately equal.</returns> + /// <param name="other">The other Rect2 to compare.</param> + /// <returns>Whether or not the Rect2s are approximately equal.</returns> public bool IsEqualApprox(Rect2 other) { return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other.Size); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs index 8f71c00d76..c27af74866 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs @@ -47,7 +47,7 @@ namespace Godot } /// <summary> - /// The area of this rect. + /// The area of this Rect2i. /// </summary> /// <value>Equivalent to <see cref="GetArea()"/>.</value> public int Area @@ -59,7 +59,7 @@ namespace Godot /// Returns a Rect2i with equivalent position and size, modified so that /// the top-left corner is the origin and width and height are positive. /// </summary> - /// <returns>The modified rect.</returns> + /// <returns>The modified Rect2i.</returns> public Rect2i Abs() { Vector2i end = End; @@ -69,10 +69,11 @@ namespace Godot /// <summary> /// Returns the intersection of this Rect2i and `b`. + /// If the rectangles do not intersect, an empty Rect2i is returned. /// </summary> - /// <param name="b">The other rect.</param> - /// <returns>The clipped rect.</returns> - public Rect2i Clip(Rect2i b) + /// <param name="b">The other Rect2i.</param> + /// <returns>The intersection of this Rect2i and `b`, or an empty Rect2i if they do not intersect.</returns> + public Rect2i Intersection(Rect2i b) { var newRect = b; @@ -96,8 +97,8 @@ namespace Godot /// <summary> /// Returns true if this Rect2i completely encloses another one. /// </summary> - /// <param name="b">The other rect that may be enclosed.</param> - /// <returns>A bool for whether or not this rect encloses `b`.</returns> + /// <param name="b">The other Rect2i that may be enclosed.</param> + /// <returns>A bool for whether or not this Rect2i encloses `b`.</returns> public bool Encloses(Rect2i b) { return b._position.x >= _position.x && b._position.y >= _position.y && @@ -109,7 +110,7 @@ namespace Godot /// Returns this Rect2i expanded to include a given point. /// </summary> /// <param name="to">The point to include.</param> - /// <returns>The expanded rect.</returns> + /// <returns>The expanded Rect2i.</returns> public Rect2i Expand(Vector2i to) { var expanded = this; @@ -151,10 +152,10 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown a given amount of units towards all the sides. + /// Returns a copy of the Rect2i grown by the specified amount on all sides. /// </summary> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown rect.</returns> + /// <returns>The grown Rect2i.</returns> public Rect2i Grow(int by) { var g = this; @@ -168,13 +169,13 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown a given amount of units towards each direction individually. + /// Returns a copy of the Rect2i grown by the specified amount on each side individually. /// </summary> - /// <param name="left">The amount to grow by on the left.</param> - /// <param name="top">The amount to grow by on the top.</param> - /// <param name="right">The amount to grow by on the right.</param> - /// <param name="bottom">The amount to grow by on the bottom.</param> - /// <returns>The grown rect.</returns> + /// <param name="left">The amount to grow by on the left side.</param> + /// <param name="top">The amount to grow by on the top side.</param> + /// <param name="right">The amount to grow by on the right side.</param> + /// <param name="bottom">The amount to grow by on the bottom side.</param> + /// <returns>The grown Rect2i.</returns> public Rect2i GrowIndividual(int left, int top, int right, int bottom) { var g = this; @@ -188,37 +189,37 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown a given amount of units towards the <see cref="Margin"/> direction. + /// Returns a copy of the Rect2i grown by the specified amount on the specified Side. /// </summary> - /// <param name="margin">The direction to grow in.</param> + /// <param name="side">The side to grow.</param> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown rect.</returns> - public Rect2i GrowMargin(Margin margin, int by) + /// <returns>The grown Rect2i.</returns> + public Rect2i GrowSide(Side side, int by) { var g = this; - g = g.GrowIndividual(Margin.Left == margin ? by : 0, - Margin.Top == margin ? by : 0, - Margin.Right == margin ? by : 0, - Margin.Bottom == margin ? by : 0); + g = g.GrowIndividual(Side.Left == side ? by : 0, + Side.Top == side ? by : 0, + Side.Right == side ? by : 0, + Side.Bottom == side ? by : 0); return g; } /// <summary> - /// Returns true if the Rect2 is flat or empty, or false otherwise. + /// Returns true if the Rect2i is flat or empty, or false otherwise. /// </summary> - /// <returns>A bool for whether or not the rect has area.</returns> + /// <returns>A bool for whether or not the Rect2i has area.</returns> public bool HasNoArea() { return _size.x <= 0 || _size.y <= 0; } /// <summary> - /// Returns true if the Rect2 contains a point, or false otherwise. + /// Returns true if the Rect2i contains a point, or false otherwise. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the rect contains `point`.</returns> + /// <returns>A bool for whether or not the Rect2i contains `point`.</returns> public bool HasPoint(Vector2i point) { if (point.x < _position.x) @@ -241,7 +242,7 @@ namespace Godot /// If `includeBorders` is true, they will also be considered overlapping /// if their borders touch, even without intersection. /// </summary> - /// <param name="b">The other rect to check for intersections with.</param> + /// <param name="b">The other Rect2i to check for intersections with.</param> /// <param name="includeBorders">Whether or not to consider borders.</param> /// <returns>A bool for whether or not they are intersecting.</returns> public bool Intersects(Rect2i b, bool includeBorders = false) @@ -273,10 +274,10 @@ namespace Godot } /// <summary> - /// Returns a larger Rect2i that contains this Rect2 and `b`. + /// Returns a larger Rect2i that contains this Rect2i and `b`. /// </summary> - /// <param name="b">The other rect.</param> - /// <returns>The merged rect.</returns> + /// <param name="b">The other Rect2i.</param> + /// <returns>The merged Rect2i.</returns> public Rect2i Merge(Rect2i b) { Rect2i newRect; diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 544f414cba..afcc75395b 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/modules/mono/glue/base_object_glue.cpp @@ -163,9 +163,9 @@ MonoObject *godot_icall_Object_weakref(Object *p_ptr) { return GDMonoUtils::unmanaged_get_managed(wref.ptr()); } -Error godot_icall_SignalAwaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, MonoObject *p_awaiter) { +int32_t godot_icall_SignalAwaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, MonoObject *p_awaiter) { StringName signal = p_signal ? *p_signal : StringName(); - return gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter); + return (int32_t)gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter); } MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr) { @@ -240,18 +240,18 @@ MonoString *godot_icall_Object_ToString(Object *p_ptr) { } void godot_register_object_icalls() { - mono_add_internal_call("Godot.Object::godot_icall_Object_Ctor", (void *)godot_icall_Object_Ctor); - mono_add_internal_call("Godot.Object::godot_icall_Object_Disposed", (void *)godot_icall_Object_Disposed); - mono_add_internal_call("Godot.Object::godot_icall_Reference_Disposed", (void *)godot_icall_Reference_Disposed); - mono_add_internal_call("Godot.Object::godot_icall_Object_ConnectEventSignals", (void *)godot_icall_Object_ConnectEventSignals); - mono_add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", (void *)godot_icall_Object_ClassDB_get_method); - mono_add_internal_call("Godot.Object::godot_icall_Object_ToString", (void *)godot_icall_Object_ToString); - mono_add_internal_call("Godot.Object::godot_icall_Object_weakref", (void *)godot_icall_Object_weakref); - mono_add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", (void *)godot_icall_SignalAwaiter_connect); - mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMemberList", (void *)godot_icall_DynamicGodotObject_SetMemberList); - mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_InvokeMember", (void *)godot_icall_DynamicGodotObject_InvokeMember); - mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_GetMember", (void *)godot_icall_DynamicGodotObject_GetMember); - mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMember", (void *)godot_icall_DynamicGodotObject_SetMember); + GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_Ctor", godot_icall_Object_Ctor); + GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_Disposed", godot_icall_Object_Disposed); + GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Reference_Disposed", godot_icall_Reference_Disposed); + GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ConnectEventSignals", godot_icall_Object_ConnectEventSignals); + GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", godot_icall_Object_ClassDB_get_method); + GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ToString", godot_icall_Object_ToString); + GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_weakref", godot_icall_Object_weakref); + GDMonoUtils::add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", godot_icall_SignalAwaiter_connect); + GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMemberList", godot_icall_DynamicGodotObject_SetMemberList); + GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_InvokeMember", godot_icall_DynamicGodotObject_InvokeMember); + GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_GetMember", godot_icall_DynamicGodotObject_GetMember); + GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMember", godot_icall_DynamicGodotObject_SetMember); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp index bb3ea0f730..dedb5b9f75 100644 --- a/modules/mono/glue/collections_glue.cpp +++ b/modules/mono/glue/collections_glue.cpp @@ -47,7 +47,7 @@ void godot_icall_Array_Dtor(Array *ptr) { memdelete(ptr); } -MonoObject *godot_icall_Array_At(Array *ptr, int index) { +MonoObject *godot_icall_Array_At(Array *ptr, int32_t index) { if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return nullptr; @@ -55,7 +55,7 @@ MonoObject *godot_icall_Array_At(Array *ptr, int index) { return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index)); } -MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class) { +MonoObject *godot_icall_Array_At_Generic(Array *ptr, int32_t index, uint32_t type_encoding, GDMonoClass *type_class) { if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return nullptr; @@ -63,7 +63,7 @@ MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_en return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index), ManagedType(type_encoding, type_class)); } -void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) { +void godot_icall_Array_SetAt(Array *ptr, int32_t index, MonoObject *value) { if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; @@ -71,11 +71,11 @@ void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) { ptr->operator[](index) = GDMonoMarshal::mono_object_to_variant(value); } -int godot_icall_Array_Count(Array *ptr) { +int32_t godot_icall_Array_Count(Array *ptr) { return ptr->size(); } -int godot_icall_Array_Add(Array *ptr, MonoObject *item) { +int32_t godot_icall_Array_Add(Array *ptr, MonoObject *item) { ptr->append(GDMonoMarshal::mono_object_to_variant(item)); return ptr->size(); } @@ -88,7 +88,7 @@ MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item) { return ptr->find(GDMonoMarshal::mono_object_to_variant(item)) != -1; } -void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) { +void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int32_t array_index) { unsigned int count = ptr->size(); if (mono_array_length(array) < (array_index + count)) { @@ -129,11 +129,11 @@ Array *godot_icall_Array_Concatenate(Array *left, Array *right) { return new_array; } -int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) { +int32_t godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) { return ptr->find(GDMonoMarshal::mono_object_to_variant(item)); } -void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item) { +void godot_icall_Array_Insert(Array *ptr, int32_t index, MonoObject *item) { if (index < 0 || index > ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; @@ -150,7 +150,7 @@ MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) { return false; } -void godot_icall_Array_RemoveAt(Array *ptr, int index) { +void godot_icall_Array_RemoveAt(Array *ptr, int32_t index) { if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; @@ -158,8 +158,8 @@ void godot_icall_Array_RemoveAt(Array *ptr, int index) { ptr->remove(index); } -Error godot_icall_Array_Resize(Array *ptr, int new_size) { - return ptr->resize(new_size); +int32_t godot_icall_Array_Resize(Array *ptr, int32_t new_size) { + return (int32_t)ptr->resize(new_size); } void godot_icall_Array_Shuffle(Array *ptr) { @@ -226,7 +226,7 @@ Array *godot_icall_Dictionary_Values(Dictionary *ptr) { return memnew(Array(ptr->values())); } -int godot_icall_Dictionary_Count(Dictionary *ptr) { +int32_t godot_icall_Dictionary_Count(Dictionary *ptr) { return ptr->size(); } @@ -308,47 +308,47 @@ MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr) { } void godot_register_collections_icalls() { - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", (void *)godot_icall_Array_Ctor); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor_MonoArray", (void *)godot_icall_Array_Ctor_MonoArray); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", (void *)godot_icall_Array_Dtor); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At", (void *)godot_icall_Array_At); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", (void *)godot_icall_Array_At_Generic); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_SetAt", (void *)godot_icall_Array_SetAt); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", (void *)godot_icall_Array_Count); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", (void *)godot_icall_Array_Add); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Concatenate", (void *)godot_icall_Array_Concatenate); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", (void *)godot_icall_Array_Duplicate); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", (void *)godot_icall_Array_IndexOf); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", (void *)godot_icall_Array_Insert); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", (void *)godot_icall_Array_Remove); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_RemoveAt", (void *)godot_icall_Array_RemoveAt); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Resize", (void *)godot_icall_Array_Resize); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Shuffle", (void *)godot_icall_Array_Shuffle); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Generic_GetElementTypeInfo", (void *)godot_icall_Array_Generic_GetElementTypeInfo); - mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_ToString", (void *)godot_icall_Array_ToString); - - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Ctor", (void *)godot_icall_Dictionary_Ctor); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Dtor", (void *)godot_icall_Dictionary_Dtor); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue", (void *)godot_icall_Dictionary_GetValue); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue_Generic", (void *)godot_icall_Dictionary_GetValue_Generic); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_SetValue", (void *)godot_icall_Dictionary_SetValue); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", (void *)godot_icall_Dictionary_Keys); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", (void *)godot_icall_Dictionary_Values); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Count", (void *)godot_icall_Dictionary_Count); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Add", (void *)godot_icall_Dictionary_Add); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", (void *)godot_icall_Dictionary_Clear); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", (void *)godot_icall_Dictionary_Contains); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", (void *)godot_icall_Dictionary_ContainsKey); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Duplicate", (void *)godot_icall_Dictionary_Duplicate); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", (void *)godot_icall_Dictionary_RemoveKey); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", (void *)godot_icall_Dictionary_Remove); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", (void *)godot_icall_Dictionary_TryGetValue); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue_Generic", (void *)godot_icall_Dictionary_TryGetValue_Generic); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Generic_GetValueTypeInfo", (void *)godot_icall_Dictionary_Generic_GetValueTypeInfo); - mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ToString", (void *)godot_icall_Dictionary_ToString); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", godot_icall_Array_Ctor); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor_MonoArray", godot_icall_Array_Ctor_MonoArray); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", godot_icall_Array_Dtor); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_At", godot_icall_Array_At); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", godot_icall_Array_At_Generic); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_SetAt", godot_icall_Array_SetAt); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", godot_icall_Array_Count); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", godot_icall_Array_Add); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", godot_icall_Array_Clear); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Concatenate", godot_icall_Array_Concatenate); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", godot_icall_Array_Contains); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", godot_icall_Array_CopyTo); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", godot_icall_Array_Duplicate); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", godot_icall_Array_IndexOf); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", godot_icall_Array_Insert); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", godot_icall_Array_Remove); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_RemoveAt", godot_icall_Array_RemoveAt); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Resize", godot_icall_Array_Resize); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Shuffle", godot_icall_Array_Shuffle); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Generic_GetElementTypeInfo", godot_icall_Array_Generic_GetElementTypeInfo); + GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_ToString", godot_icall_Array_ToString); + + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Ctor", godot_icall_Dictionary_Ctor); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Dtor", godot_icall_Dictionary_Dtor); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue", godot_icall_Dictionary_GetValue); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue_Generic", godot_icall_Dictionary_GetValue_Generic); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_SetValue", godot_icall_Dictionary_SetValue); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", godot_icall_Dictionary_Keys); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", godot_icall_Dictionary_Values); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Count", godot_icall_Dictionary_Count); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Add", godot_icall_Dictionary_Add); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", godot_icall_Dictionary_Clear); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", godot_icall_Dictionary_Contains); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", godot_icall_Dictionary_ContainsKey); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Duplicate", godot_icall_Dictionary_Duplicate); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", godot_icall_Dictionary_RemoveKey); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", godot_icall_Dictionary_Remove); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", godot_icall_Dictionary_TryGetValue); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue_Generic", godot_icall_Dictionary_TryGetValue_Generic); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Generic_GetValueTypeInfo", godot_icall_Dictionary_Generic_GetValueTypeInfo); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ToString", godot_icall_Dictionary_ToString); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index 58d8dceb25..a4566b82fb 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -289,33 +289,33 @@ MonoObject *godot_icall_DefaultGodotTaskScheduler() { } void godot_register_gd_icalls() { - mono_add_internal_call("Godot.GD::godot_icall_GD_bytes2var", (void *)godot_icall_GD_bytes2var); - mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert); - mono_add_internal_call("Godot.GD::godot_icall_GD_hash", (void *)godot_icall_GD_hash); - mono_add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", (void *)godot_icall_GD_instance_from_id); - mono_add_internal_call("Godot.GD::godot_icall_GD_pusherror", (void *)godot_icall_GD_pusherror); - mono_add_internal_call("Godot.GD::godot_icall_GD_pushwarning", (void *)godot_icall_GD_pushwarning); - mono_add_internal_call("Godot.GD::godot_icall_GD_print", (void *)godot_icall_GD_print); - mono_add_internal_call("Godot.GD::godot_icall_GD_printerr", (void *)godot_icall_GD_printerr); - mono_add_internal_call("Godot.GD::godot_icall_GD_printraw", (void *)godot_icall_GD_printraw); - mono_add_internal_call("Godot.GD::godot_icall_GD_prints", (void *)godot_icall_GD_prints); - mono_add_internal_call("Godot.GD::godot_icall_GD_printt", (void *)godot_icall_GD_printt); - mono_add_internal_call("Godot.GD::godot_icall_GD_randf", (void *)godot_icall_GD_randf); - mono_add_internal_call("Godot.GD::godot_icall_GD_randi", (void *)godot_icall_GD_randi); - mono_add_internal_call("Godot.GD::godot_icall_GD_randomize", (void *)godot_icall_GD_randomize); - mono_add_internal_call("Godot.GD::godot_icall_GD_randf_range", (void *)godot_icall_GD_randf_range); - mono_add_internal_call("Godot.GD::godot_icall_GD_randi_range", (void *)godot_icall_GD_randi_range); - mono_add_internal_call("Godot.GD::godot_icall_GD_rand_seed", (void *)godot_icall_GD_rand_seed); - mono_add_internal_call("Godot.GD::godot_icall_GD_seed", (void *)godot_icall_GD_seed); - mono_add_internal_call("Godot.GD::godot_icall_GD_str", (void *)godot_icall_GD_str); - mono_add_internal_call("Godot.GD::godot_icall_GD_str2var", (void *)godot_icall_GD_str2var); - mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists); - mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes); - mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str); - mono_add_internal_call("Godot.GD::godot_icall_TypeToVariantType", (void *)godot_icall_TypeToVariantType); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_bytes2var", godot_icall_GD_bytes2var); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_convert", godot_icall_GD_convert); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_hash", godot_icall_GD_hash); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", godot_icall_GD_instance_from_id); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pusherror", godot_icall_GD_pusherror); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pushwarning", godot_icall_GD_pushwarning); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_print", godot_icall_GD_print); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printerr", godot_icall_GD_printerr); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printraw", godot_icall_GD_printraw); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_prints", godot_icall_GD_prints); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printt", godot_icall_GD_printt); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randf", godot_icall_GD_randf); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randi", godot_icall_GD_randi); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randomize", godot_icall_GD_randomize); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randf_range", godot_icall_GD_randf_range); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randi_range", godot_icall_GD_randi_range); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_rand_seed", godot_icall_GD_rand_seed); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_seed", godot_icall_GD_seed); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_str", godot_icall_GD_str); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_str2var", godot_icall_GD_str2var); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_type_exists", godot_icall_GD_type_exists); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_var2bytes", godot_icall_GD_var2bytes); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_var2str", godot_icall_GD_var2str); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_TypeToVariantType", godot_icall_TypeToVariantType); // Dispatcher - mono_add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", (void *)godot_icall_DefaultGodotTaskScheduler); + GDMonoUtils::add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", godot_icall_DefaultGodotTaskScheduler); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/nodepath_glue.cpp b/modules/mono/glue/nodepath_glue.cpp index 09c6d8f482..9405360c6c 100644 --- a/modules/mono/glue/nodepath_glue.cpp +++ b/modules/mono/glue/nodepath_glue.cpp @@ -81,17 +81,17 @@ MonoBoolean godot_icall_NodePath_is_empty(NodePath *p_ptr) { } void godot_register_nodepath_icalls() { - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_Ctor", (void *)godot_icall_NodePath_Ctor); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_Dtor", (void *)godot_icall_NodePath_Dtor); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_operator_String", (void *)godot_icall_NodePath_operator_String); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_as_property_path", (void *)godot_icall_NodePath_get_as_property_path); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_subnames", (void *)godot_icall_NodePath_get_concatenated_subnames); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name", (void *)godot_icall_NodePath_get_name); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name_count", (void *)godot_icall_NodePath_get_name_count); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname", (void *)godot_icall_NodePath_get_subname); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname_count", (void *)godot_icall_NodePath_get_subname_count); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_is_absolute", (void *)godot_icall_NodePath_is_absolute); - mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_is_empty", (void *)godot_icall_NodePath_is_empty); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Ctor", godot_icall_NodePath_Ctor); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Dtor", godot_icall_NodePath_Dtor); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_operator_String", godot_icall_NodePath_operator_String); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_as_property_path", godot_icall_NodePath_get_as_property_path); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_subnames", godot_icall_NodePath_get_concatenated_subnames); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name", godot_icall_NodePath_get_name); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name_count", godot_icall_NodePath_get_name_count); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname", godot_icall_NodePath_get_subname); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname_count", godot_icall_NodePath_get_subname_count); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_is_absolute", godot_icall_NodePath_is_absolute); + GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_is_empty", godot_icall_NodePath_is_empty); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/rid_glue.cpp b/modules/mono/glue/rid_glue.cpp index cb4f26511f..410a98aa84 100644 --- a/modules/mono/glue/rid_glue.cpp +++ b/modules/mono/glue/rid_glue.cpp @@ -56,9 +56,9 @@ uint32_t godot_icall_RID_get_id(RID *p_ptr) { } void godot_register_rid_icalls() { - mono_add_internal_call("Godot.RID::godot_icall_RID_Ctor", (void *)godot_icall_RID_Ctor); - mono_add_internal_call("Godot.RID::godot_icall_RID_Dtor", (void *)godot_icall_RID_Dtor); - mono_add_internal_call("Godot.RID::godot_icall_RID_get_id", (void *)godot_icall_RID_get_id); + GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_Ctor", godot_icall_RID_Ctor); + GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_Dtor", godot_icall_RID_Dtor); + GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_get_id", godot_icall_RID_get_id); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/scene_tree_glue.cpp b/modules/mono/glue/scene_tree_glue.cpp index 53d6c1436d..a96918dafa 100644 --- a/modules/mono/glue/scene_tree_glue.cpp +++ b/modules/mono/glue/scene_tree_glue.cpp @@ -48,7 +48,7 @@ Array *godot_icall_SceneTree_get_nodes_in_group_Generic(SceneTree *ptr, StringNa ptr->get_nodes_in_group(*group, &nodes); // No need to bother if the group is empty - if (!nodes.empty()) { + if (!nodes.is_empty()) { MonoType *elem_type = mono_reflection_type_get_type(refltype); MonoClass *mono_class = mono_class_from_mono_type(elem_type); GDMonoClass *klass = GDMono::get_singleton()->get_class(mono_class); @@ -80,7 +80,7 @@ Array *godot_icall_SceneTree_get_nodes_in_group_Generic(SceneTree *ptr, StringNa } void godot_register_scene_tree_icalls() { - mono_add_internal_call("Godot.SceneTree::godot_icall_SceneTree_get_nodes_in_group_Generic", (void *)godot_icall_SceneTree_get_nodes_in_group_Generic); + GDMonoUtils::add_internal_call("Godot.SceneTree::godot_icall_SceneTree_get_nodes_in_group_Generic", godot_icall_SceneTree_get_nodes_in_group_Generic); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp index 9271731573..d71d175418 100644 --- a/modules/mono/glue/string_glue.cpp +++ b/modules/mono/glue/string_glue.cpp @@ -68,12 +68,12 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) { } void godot_register_string_icalls() { - mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer); - mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text); - mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", (void *)godot_icall_String_rfind); - mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn); - mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer); - mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text); + GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", godot_icall_String_md5_buffer); + GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", godot_icall_String_md5_text); + GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", godot_icall_String_rfind); + GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", godot_icall_String_rfindn); + GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", godot_icall_String_sha256_buffer); + GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", godot_icall_String_sha256_text); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/string_name_glue.cpp b/modules/mono/glue/string_name_glue.cpp index 9323e3bbb3..fc2bf32b95 100644 --- a/modules/mono/glue/string_name_glue.cpp +++ b/modules/mono/glue/string_name_glue.cpp @@ -53,10 +53,10 @@ MonoBoolean godot_icall_StringName_is_empty(StringName *p_ptr) { } void godot_register_string_name_icalls() { - mono_add_internal_call("Godot.StringName::godot_icall_StringName_Ctor", (void *)godot_icall_StringName_Ctor); - mono_add_internal_call("Godot.StringName::godot_icall_StringName_Dtor", (void *)godot_icall_StringName_Dtor); - mono_add_internal_call("Godot.StringName::godot_icall_StringName_operator_String", (void *)godot_icall_StringName_operator_String); - mono_add_internal_call("Godot.StringName::godot_icall_StringName_is_empty", (void *)godot_icall_StringName_is_empty); + GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_Ctor", godot_icall_StringName_Ctor); + GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_Dtor", godot_icall_StringName_Dtor); + GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_operator_String", godot_icall_StringName_operator_String); + GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_is_empty", godot_icall_StringName_is_empty); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 093a935288..e0ddf5cd33 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -146,7 +146,7 @@ private: String appname = ProjectSettings::get_singleton()->get("application/config/name"); String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.empty()) { + if (appname_safe.is_empty()) { appname_safe = "UnnamedProject"; } diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 772961291c..db33151eb3 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -142,7 +142,7 @@ void gd_mono_debug_init() { int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000); if (Engine::get_singleton()->is_editor_hint() || - ProjectSettings::get_singleton()->get_resource_path().empty() || + ProjectSettings::get_singleton()->get_resource_path().is_empty() || Main::is_project_manager()) { if (da_args.size() == 0) { return; @@ -297,7 +297,7 @@ void GDMono::determine_mono_dirs(String &r_assembly_rootdir, String &r_config_di } #ifdef WINDOWS_ENABLED - if (r_assembly_rootdir.empty() || r_config_dir.empty()) { + if (r_assembly_rootdir.is_empty() || r_config_dir.is_empty()) { ERR_PRINT("Cannot find Mono in the registry."); // Assertion: if they are not set, then they weren't found in the registry CRASH_COND(mono_reg_info.assembly_dir.length() > 0 || mono_reg_info.config_dir.length() > 0); @@ -360,7 +360,7 @@ void GDMono::initialize() { #ifndef TOOLS_ENABLED // Exported games that don't use C# must still work. They likely don't ship with mscorlib. // We only initialize the Mono runtime if we can find mscorlib. Otherwise it would crash. - if (GDMonoAssembly::find_assembly("mscorlib.dll").empty()) { + if (GDMonoAssembly::find_assembly("mscorlib.dll").is_empty()) { print_verbose("Mono: Skipping runtime initialization because 'mscorlib.dll' could not be found"); return; } @@ -944,7 +944,7 @@ void GDMono::_load_api_assemblies() { // 2. Update the API assemblies String update_error = update_api_assemblies_from_prebuilt("Debug", &core_api_assembly.out_of_sync, &editor_api_assembly.out_of_sync); - CRASH_COND_MSG(!update_error.empty(), update_error); + CRASH_COND_MSG(!update_error.is_empty(), update_error); // 3. Load the scripts domain again Error domain_load_err = _load_scripts_domain(); @@ -998,7 +998,7 @@ bool GDMono::_load_project_assembly() { String appname = ProjectSettings::get_singleton()->get("application/config/name"); String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.empty()) { + if (appname_safe.is_empty()) { appname_safe = "UnnamedProject"; } diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 33628b3ce3..b3e30cdecd 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -48,20 +48,20 @@ Vector<String> GDMonoAssembly::search_dirs; void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) { String framework_dir; - if (!p_custom_bcl_dir.empty()) { + if (!p_custom_bcl_dir.is_empty()) { framework_dir = p_custom_bcl_dir; } else if (mono_assembly_getrootdir()) { framework_dir = String::utf8(mono_assembly_getrootdir()).plus_file("mono").plus_file("4.5"); } - if (!framework_dir.empty()) { + if (!framework_dir.is_empty()) { r_search_dirs.push_back(framework_dir); r_search_dirs.push_back(framework_dir.plus_file("Facades")); } #if !defined(TOOLS_ENABLED) String data_game_assemblies_dir = GodotSharpDirs::get_data_game_assemblies_dir(); - if (!data_game_assemblies_dir.empty()) { + if (!data_game_assemblies_dir.is_empty()) { r_search_dirs.push_back(data_game_assemblies_dir); } #endif @@ -72,7 +72,7 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); } - if (p_custom_config.empty()) { + if (p_custom_config.is_empty()) { r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); } else { String api_config = p_custom_config == "ExportRelease" ? "Release" : "Debug"; @@ -230,7 +230,7 @@ void GDMonoAssembly::initialize() { MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) { Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); - ERR_FAIL_COND_V_MSG(data.empty(), nullptr, "Could read the assembly in the specified location"); + ERR_FAIL_COND_V_MSG(data.is_empty(), nullptr, "Could read the assembly in the specified location"); String image_filename; @@ -423,7 +423,7 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) match = current; } - while (!nested_classes.empty()) { + while (!nested_classes.is_empty()) { GDMonoClass *current_nested = nested_classes.front()->get(); nested_classes.pop_front(); diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index faa3d24eeb..5370b1c6fc 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -36,6 +36,9 @@ #include "../mono_gc_handle.h" #include "../utils/macros.h" #include "gd_mono_header.h" +#ifdef JAVASCRIPT_ENABLED +#include "gd_mono_wasm_m2n.h" +#endif #include "core/object/class_db.h" #include "core/object/reference.h" @@ -154,6 +157,22 @@ private: }; StringName get_native_godot_class_name(GDMonoClass *p_class); + +template <typename... P> +void add_internal_call(const char *p_name, void (*p_func)(P...)) { +#ifdef JAVASCRIPT_ENABLED + GDMonoWasmM2n::ICallTrampolines<P...>::add(); +#endif + mono_add_internal_call(p_name, (void *)p_func); +} + +template <typename R, typename... P> +void add_internal_call(const char *p_name, R (*p_func)(P...)) { +#ifdef JAVASCRIPT_ENABLED + GDMonoWasmM2n::ICallTrampolinesR<R, P...>::add(); +#endif + mono_add_internal_call(p_name, (void *)p_func); +} } // namespace GDMonoUtils #define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoUtils::get_native_godot_class_name(m_class)) diff --git a/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp b/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp new file mode 100644 index 0000000000..f4c964c6eb --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* gd_mono_wasm_m2n.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "gd_mono_wasm_m2n.h" + +#ifdef JAVASCRIPT_ENABLED + +#include "core/templates/oa_hash_map.h" + +typedef mono_bool (*GodotMonoM2nIcallTrampolineDispatch)(const char *cookie, void *target_func, Mono_InterpMethodArguments *margs); + +// This extern function is implemented in our patched version of Mono +MONO_API void godot_mono_register_m2n_icall_trampoline_dispatch_hook(GodotMonoM2nIcallTrampolineDispatch hook); + +namespace GDMonoWasmM2n { + +struct HashMapCookieComparator { + static bool compare(const char *p_lhs, const char *p_rhs) { + return strcmp(p_lhs, p_rhs) == 0; + } +}; + +// The default hasher supports 'const char *' C Strings, but we need a custom comparator +OAHashMap<const char *, TrampolineFunc, HashMapHasherDefault, HashMapCookieComparator> trampolines; + +void set_trampoline(const char *cookies, GDMonoWasmM2n::TrampolineFunc trampoline_func) { + trampolines.set(cookies, trampoline_func); +} + +mono_bool trampoline_dispatch_hook(const char *cookie, void *target_func, Mono_InterpMethodArguments *margs) { + TrampolineFunc *trampoline_func = trampolines.lookup_ptr(cookie); + + if (!trampoline_func) { + return false; + } + + (*trampoline_func)(target_func, margs); + return true; +} + +bool initialized = false; + +void lazy_initialize() { + // Doesn't need to be thread safe + if (!initialized) { + initialized = true; + godot_mono_register_m2n_icall_trampoline_dispatch_hook(&trampoline_dispatch_hook); + } +} +} // namespace GDMonoWasmM2n + +#endif diff --git a/modules/mono/mono_gd/gd_mono_wasm_m2n.h b/modules/mono/mono_gd/gd_mono_wasm_m2n.h new file mode 100644 index 0000000000..68a14f15f4 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_wasm_m2n.h @@ -0,0 +1,263 @@ +/*************************************************************************/ +/* gd_mono_wasm_m2n.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 GD_MONO_WASM_M2N_H +#define GD_MONO_WASM_M2N_H + +#ifdef JAVASCRIPT_ENABLED + +#include "core/string/ustring.h" +#include "core/typedefs.h" + +#include <mono/metadata/loader.h> +#include <mono/utils/mono-publib.h> +#include <stdexcept> +#include <type_traits> + +extern "C" { + +struct Mono_InterpMethodArguments { + size_t ilen; + void **iargs; + size_t flen; + double *fargs; + void **retval; + size_t is_float_ret; + //#ifdef TARGET_WASM + void *sig; + //#endif +}; +} // extern "C" + +namespace GDMonoWasmM2n { + +template <typename T, size_t Size> +struct array { + T elems[Size]; +}; + +template <typename T> +constexpr char get_m2n_cookie_impl() { +#define M2N_REG_COOKIE(m_type, m_cookie) \ + if constexpr (std::is_same_v<m_type, T>) { \ + return m_cookie; \ + } + + M2N_REG_COOKIE(MonoBoolean, 'I'); + M2N_REG_COOKIE(int8_t, 'I'); + M2N_REG_COOKIE(uint8_t, 'I'); + M2N_REG_COOKIE(int16_t, 'I'); + M2N_REG_COOKIE(uint16_t, 'I'); + M2N_REG_COOKIE(int32_t, 'I'); + M2N_REG_COOKIE(uint32_t, 'I'); + M2N_REG_COOKIE(int64_t, 'L'); + M2N_REG_COOKIE(uint64_t, 'L'); + M2N_REG_COOKIE(float, 'F'); + M2N_REG_COOKIE(double, 'D'); + + if constexpr (std::is_pointer_v<T>) { + if constexpr (sizeof(void *) == 4) { + return 'I'; + } else { + return 'L'; + } + } + + if constexpr (std::is_void_v<T>) { + return 'V'; + } + + return 'X'; + +#undef M2N_REG_COOKIE +} + +template <typename T> +constexpr char get_m2n_cookie() { + constexpr char cookie = get_m2n_cookie_impl<T>(); + static_assert(cookie != 'X', "Type not supported in internal call signature."); + return cookie; +} + +template <typename... T> +constexpr array<const char, sizeof...(T) + 2> get_m2n_cookies() { + return array<const char, sizeof...(T) + 2>{ 'V', get_m2n_cookie<T>()..., '\0' }; +} + +template <typename R, typename... T> +constexpr array<const char, sizeof...(T) + 2> get_m2n_cookies_r() { + return array<const char, sizeof...(T) + 2>{ get_m2n_cookie<R>(), get_m2n_cookie<T>()..., '\0' }; +} + +template <typename T> +constexpr size_t calc_m2n_index(size_t &r_int_idx, size_t &r_float_idx) { + constexpr char cookie = get_m2n_cookie<T>(); + + static_assert(cookie == 'I' || cookie == 'L' || cookie == 'F' || cookie == 'D'); + + if constexpr (cookie == 'I' || cookie == 'L') { + size_t ret = r_int_idx; + r_int_idx += cookie == 'I' ? 1 : 2; + return ret; + } else { + size_t ret = r_float_idx; + r_float_idx += cookie == 'F' ? 1 : 2; + return ret; + } +} + +template <typename... P> +constexpr array<size_t, sizeof...(P)> get_indices_for_type() { + size_t int_idx = 0; + size_t float_idx = 0; + return array<size_t, sizeof...(P)>{ calc_m2n_index<P>(int_idx, float_idx)... }; +} + +constexpr size_t fidx(size_t p_x) { + if constexpr (sizeof(void *) == 4) { + return p_x * 2; + } else { + return p_x; + } +} + +template <typename T> +T m2n_arg_cast(Mono_InterpMethodArguments *p_margs, size_t p_idx) { + constexpr char cookie = get_m2n_cookie<T>(); + + static_assert(cookie == 'I' || cookie == 'L' || cookie == 'F' || cookie == 'D'); + + if constexpr (cookie == 'I') { + return (T)(size_t)p_margs->iargs[p_idx]; + } else if constexpr (cookie == 'L') { + static_assert(std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t> || + (sizeof(void *) == 8 && std::is_pointer_v<T>), + "Invalid type for cookie 'L'."); + + union { + T l; + struct { + int32_t lo; + int32_t hi; + } pair; + } p; + + p.pair.lo = (int32_t)(size_t)p_margs->iargs[p_idx]; + p.pair.hi = (int32_t)(size_t)p_margs->iargs[p_idx + 1]; + + return p.l; + } else if constexpr (cookie == 'F') { + return *reinterpret_cast<float *>(&p_margs->fargs[fidx(p_idx)]); + } else if constexpr (cookie == 'D') { + return (T)(size_t)p_margs->fargs[p_idx]; + } +} + +template <typename... P, size_t... Is> +void m2n_trampoline_with_idx_seq(void *p_target_func, Mono_InterpMethodArguments *p_margs, IndexSequence<Is...>) { + constexpr array<size_t, sizeof...(P)> indices = get_indices_for_type<P...>(); + typedef void (*Func)(P...); + Func func = (Func)p_target_func; + func(m2n_arg_cast<P>(p_margs, indices.elems[Is])...); +} + +template <typename R, typename... P, size_t... Is> +void m2n_trampoline_with_idx_seq_r(void *p_target_func, Mono_InterpMethodArguments *p_margs, IndexSequence<Is...>) { + constexpr array<size_t, sizeof...(P)> indices = get_indices_for_type<P...>(); + typedef R (*Func)(P...); + Func func = (Func)p_target_func; + R res = func(m2n_arg_cast<P>(p_margs, indices.elems[Is])...); + *reinterpret_cast<R *>(p_margs->retval) = res; +} + +inline void m2n_trampoline_with_idx_seq_0(void *p_target_func, Mono_InterpMethodArguments *p_margs) { + typedef void (*Func)(); + Func func = (Func)p_target_func; + func(); +} + +template <typename R> +void m2n_trampoline_with_idx_seq_r0(void *p_target_func, Mono_InterpMethodArguments *p_margs) { + typedef R (*Func)(); + Func func = (Func)p_target_func; + R res = func(); + *reinterpret_cast<R *>(p_margs->retval) = res; +} + +template <typename... P> +void m2n_trampoline(void *p_target_func, Mono_InterpMethodArguments *p_margs) { + if constexpr (sizeof...(P) == 0) { + m2n_trampoline_with_idx_seq_0(p_target_func, p_margs); + } else { + m2n_trampoline_with_idx_seq<P...>(p_target_func, p_margs, BuildIndexSequence<sizeof...(P)>{}); + } +} + +template <typename R, typename... P> +void m2n_trampoline_r(void *p_target_func, Mono_InterpMethodArguments *p_margs) { + if constexpr (sizeof...(P) == 0) { + m2n_trampoline_with_idx_seq_r0<R>(p_target_func, p_margs); + } else { + m2n_trampoline_with_idx_seq_r<R, P...>(p_target_func, p_margs, BuildIndexSequence<sizeof...(P)>{}); + } +} + +typedef void (*TrampolineFunc)(void *p_target_func, Mono_InterpMethodArguments *p_margs); + +void set_trampoline(const char *cookies, TrampolineFunc trampoline_func); + +void lazy_initialize(); + +template <typename... P> +struct ICallTrampolines { + static constexpr auto cookies = get_m2n_cookies<P...>(); + + static void add() { + lazy_initialize(); + set_trampoline(cookies.elems, &m2n_trampoline<P...>); + } +}; + +template <typename R, typename... P> +struct ICallTrampolinesR { + static constexpr auto cookies = get_m2n_cookies_r<R, P...>(); + + static void add() { + lazy_initialize(); + set_trampoline(cookies.elems, &m2n_trampoline_r<R, P...>); + } +}; + +void initialize(); +} // namespace GDMonoWasmM2n + +#endif + +#endif // GD_MONO_WASM_M2N_H diff --git a/modules/mono/mono_gd/support/android_support.cpp b/modules/mono/mono_gd/support/android_support.cpp index 18daf859b5..bc2ae03299 100644 --- a/modules/mono/mono_gd/support/android_support.cpp +++ b/modules/mono/mono_gd/support/android_support.cpp @@ -355,8 +355,8 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { } void register_internal_calls() { - mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store); - mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup); + GDMonoUtils::add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", _gd_mono_init_cert_store); + GDMonoUtils::add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", _gd_mono_android_cert_store_lookup); } void initialize() { diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index a24097924e..039afa8411 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -136,7 +136,7 @@ String realpath(const String &p_path) { } String join(const String &p_a, const String &p_b) { - if (p_a.empty()) { + if (p_a.is_empty()) { return p_b; } @@ -165,7 +165,7 @@ String relative_to_impl(const String &p_path, const String &p_relative_to) { } else { String base_dir = p_relative_to.get_base_dir(); - if (base_dir.length() <= 2 && (base_dir.empty() || base_dir.ends_with(":"))) { + if (base_dir.length() <= 2 && (base_dir.is_empty() || base_dir.ends_with(":"))) { return p_path; } diff --git a/modules/ogg/SCsub b/modules/ogg/SCsub index e768fb4ae8..e415d92498 100644 --- a/modules/ogg/SCsub +++ b/modules/ogg/SCsub @@ -9,6 +9,9 @@ Import("env_modules") env_ogg = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + if env["builtin_libogg"]: thirdparty_dir = "#thirdparty/libogg/" thirdparty_sources = [ @@ -21,7 +24,16 @@ if env["builtin_libogg"]: env_thirdparty = env_ogg.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + # Godot source files -env_ogg.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_ogg.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/opensimplex/SCsub b/modules/opensimplex/SCsub index 52d8b145ef..86d77c3dfb 100644 --- a/modules/opensimplex/SCsub +++ b/modules/opensimplex/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_opensimplex = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + thirdparty_dir = "#thirdparty/misc/" thirdparty_sources = [ "open-simplex-noise.c", @@ -16,7 +19,15 @@ env_opensimplex.Prepend(CPPPATH=[thirdparty_dir]) env_thirdparty = env_opensimplex.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot source files + +module_obj = [] + +env_opensimplex.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -# Godot's own source files -env_opensimplex.add_source_files(env.modules_sources, "*.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/opus/SCsub b/modules/opus/SCsub index 52c61fa708..1437cd86df 100644 --- a/modules/opus/SCsub +++ b/modules/opus/SCsub @@ -10,6 +10,10 @@ Import("env_modules") env_opus = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + +# Thirdparty source files if env["builtin_opus"]: thirdparty_dir = "#thirdparty/opus/" @@ -233,7 +237,16 @@ if env["builtin_opus"]: env_thirdparty = env_opus.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + + +# Godot source files + +module_obj = [] + +env_opus.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -# Module files -env_opus.add_source_files(env.modules_sources, "register_types.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/pvr/SCsub b/modules/pvr/SCsub index e0baf851f1..36052cffed 100644 --- a/modules/pvr/SCsub +++ b/modules/pvr/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_pvr = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + # Not unbundled so far since not widespread as shared library thirdparty_dir = "#thirdparty/pvrtccompressor/" thirdparty_sources = [ @@ -21,7 +24,15 @@ env_pvr.Prepend(CPPPATH=[thirdparty_dir]) env_thirdparty = env_pvr.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj # Godot source files -env_pvr.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_pvr.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/pvr/image_compress_pvrtc.cpp b/modules/pvr/image_compress_pvrtc.cpp new file mode 100644 index 0000000000..6695a539d0 --- /dev/null +++ b/modules/pvr/image_compress_pvrtc.cpp @@ -0,0 +1,84 @@ +/*************************************************************************/ +/* image_compress_pvrtc.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "image_compress_pvrtc.h" + +#include "core/io/image.h" +#include "core/object/reference.h" + +#include <PvrTcEncoder.h> +#include <RgbaBitmap.h> + +static void _compress_pvrtc1_4bpp(Image *p_img) { + Ref<Image> img = p_img->duplicate(); + + bool make_mipmaps = false; + if (!img->is_size_po2() || img->get_width() != img->get_height()) { + make_mipmaps = img->has_mipmaps(); + img->resize_to_po2(true); + } + img->convert(Image::FORMAT_RGBA8); + if (!img->has_mipmaps() && make_mipmaps) { + img->generate_mipmaps(); + } + + bool use_alpha = img->detect_alpha(); + + Ref<Image> new_img; + new_img.instance(); + new_img->create(img->get_width(), img->get_height(), img->has_mipmaps(), use_alpha ? Image::FORMAT_PVRTC1_4A : Image::FORMAT_PVRTC1_4); + + Vector<uint8_t> data = new_img->get_data(); + { + uint8_t *wr = data.ptrw(); + const uint8_t *r = img->get_data().ptr(); + + for (int i = 0; i <= new_img->get_mipmap_count(); i++) { + int ofs, size, w, h; + img->get_mipmap_offset_size_and_dimensions(i, ofs, size, w, h); + Javelin::RgbaBitmap bm(w, h); + void *dst = (void *)bm.GetData(); + copymem(dst, &r[ofs], size); + Javelin::ColorRgba<unsigned char> *dp = bm.GetData(); + for (int j = 0; j < size / 4; j++) { + // Red and blue colors are swapped. + SWAP(dp[j].r, dp[j].b); + } + new_img->get_mipmap_offset_size_and_dimensions(i, ofs, size, w, h); + Javelin::PvrTcEncoder::EncodeRgba4Bpp(&wr[ofs], bm); + } + } + + p_img->create(new_img->get_width(), new_img->get_height(), new_img->has_mipmaps(), new_img->get_format(), data); +} + +void _register_pvrtc_compress_func() { + Image::_image_compress_pvrtc1_4bpp_func = _compress_pvrtc1_4bpp; +} diff --git a/modules/pvr/image_compress_pvrtc.h b/modules/pvr/image_compress_pvrtc.h new file mode 100644 index 0000000000..fde65f4bbe --- /dev/null +++ b/modules/pvr/image_compress_pvrtc.h @@ -0,0 +1,36 @@ +/*************************************************************************/ +/* image_compress_pvrtc.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 IMAGE_COMPRESS_PVRTC_H +#define IMAGE_COMPRESS_PVRTC_H + +void _register_pvrtc_compress_func(); + +#endif // IMAGE_COMPRESS_PVRTC_H diff --git a/modules/pvr/register_types.cpp b/modules/pvr/register_types.cpp index 1eb697bba3..9bfc334d76 100644 --- a/modules/pvr/register_types.cpp +++ b/modules/pvr/register_types.cpp @@ -30,6 +30,7 @@ #include "register_types.h" +#include "image_compress_pvrtc.h" #include "texture_loader_pvr.h" static Ref<ResourceFormatPVR> resource_loader_pvr; @@ -37,6 +38,8 @@ static Ref<ResourceFormatPVR> resource_loader_pvr; void register_pvr_types() { resource_loader_pvr.instance(); ResourceLoader::add_resource_format_loader(resource_loader_pvr); + + _register_pvrtc_compress_func(); } void unregister_pvr_types() { diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index 0923714387..40847cf9cb 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -29,11 +29,8 @@ /*************************************************************************/ #include "texture_loader_pvr.h" -#include "PvrTcEncoder.h" -#include "RgbaBitmap.h" + #include "core/os/file_access.h" -#include <string.h> -#include <new> static void _pvrtc_decompress(Image *p_img); @@ -111,11 +108,11 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, switch (flags & 0xFF) { case 0x18: case 0xC: - format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC2A : Image::FORMAT_PVRTC2; + format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC1_2A : Image::FORMAT_PVRTC1_2; break; case 0x19: case 0xD: - format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC4A : Image::FORMAT_PVRTC4; + format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC1_4A : Image::FORMAT_PVRTC1_4; break; case 0x16: format = Image::FORMAT_L8; @@ -156,7 +153,7 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, } Ref<Image> image = memnew(Image(width, height, mipmaps, format, data)); - ERR_FAIL_COND_V(image->empty(), RES()); + ERR_FAIL_COND_V(image->is_empty(), RES()); Ref<ImageTexture> texture = memnew(ImageTexture); texture->create_from_image(image); @@ -183,53 +180,8 @@ String ResourceFormatPVR::get_resource_type(const String &p_path) const { return ""; } -static void _compress_pvrtc4(Image *p_img) { - Ref<Image> img = p_img->duplicate(); - - bool make_mipmaps = false; - if (!img->is_size_po2() || img->get_width() != img->get_height()) { - make_mipmaps = img->has_mipmaps(); - img->resize_to_po2(true); - } - img->convert(Image::FORMAT_RGBA8); - if (!img->has_mipmaps() && make_mipmaps) { - img->generate_mipmaps(); - } - - bool use_alpha = img->detect_alpha(); - - Ref<Image> new_img; - new_img.instance(); - new_img->create(img->get_width(), img->get_height(), img->has_mipmaps(), use_alpha ? Image::FORMAT_PVRTC4A : Image::FORMAT_PVRTC4); - - Vector<uint8_t> data = new_img->get_data(); - { - uint8_t *wr = data.ptrw(); - const uint8_t *r = img->get_data().ptr(); - - for (int i = 0; i <= new_img->get_mipmap_count(); i++) { - int ofs, size, w, h; - img->get_mipmap_offset_size_and_dimensions(i, ofs, size, w, h); - Javelin::RgbaBitmap bm(w, h); - void *dst = (void *)bm.GetData(); - copymem(dst, &r[ofs], size); - Javelin::ColorRgba<unsigned char> *dp = bm.GetData(); - for (int j = 0; j < size / 4; j++) { - /* red and blue colors are swapped. */ - SWAP(dp[j].r, dp[j].b); - } - new_img->get_mipmap_offset_size_and_dimensions(i, ofs, size, w, h); - Javelin::PvrTcEncoder::EncodeRgba4Bpp(&wr[ofs], bm); - } - } - - p_img->create(new_img->get_width(), new_img->get_height(), new_img->has_mipmaps(), new_img->get_format(), data); -} - ResourceFormatPVR::ResourceFormatPVR() { Image::_image_decompress_pvrtc = _pvrtc_decompress; - Image::_image_compress_pvrtc4_func = _compress_pvrtc4; - Image::_image_compress_pvrtc2_func = _compress_pvrtc4; } ///////////////////////////////////////////////////////// @@ -635,9 +587,9 @@ static void decompress_pvrtc(PVRTCBlock *p_comp_img, const int p_2bit, const int } static void _pvrtc_decompress(Image *p_img) { - ERR_FAIL_COND(p_img->get_format() != Image::FORMAT_PVRTC2 && p_img->get_format() != Image::FORMAT_PVRTC2A && p_img->get_format() != Image::FORMAT_PVRTC4 && p_img->get_format() != Image::FORMAT_PVRTC4A); + ERR_FAIL_COND(p_img->get_format() != Image::FORMAT_PVRTC1_2 && p_img->get_format() != Image::FORMAT_PVRTC1_2A && p_img->get_format() != Image::FORMAT_PVRTC1_4 && p_img->get_format() != Image::FORMAT_PVRTC1_4A); - bool _2bit = (p_img->get_format() == Image::FORMAT_PVRTC2 || p_img->get_format() == Image::FORMAT_PVRTC2A); + bool _2bit = (p_img->get_format() == Image::FORMAT_PVRTC1_2 || p_img->get_format() == Image::FORMAT_PVRTC1_2A); Vector<uint8_t> data = p_img->get_data(); const uint8_t *r = data.ptr(); diff --git a/modules/regex/SCsub b/modules/regex/SCsub index 2afacc1d9c..deb9db7591 100644 --- a/modules/regex/SCsub +++ b/modules/regex/SCsub @@ -5,6 +5,10 @@ Import("env_modules") env_regex = env_modules.Clone() +# Thirdparty source files + +thirdparty_obj = [] + if env["builtin_pcre2"]: thirdparty_dir = "#thirdparty/pcre2/src/" thirdparty_flags = ["PCRE2_STATIC", "HAVE_CONFIG_H", "SUPPORT_UNICODE"] @@ -52,11 +56,21 @@ if env["builtin_pcre2"]: env_pcre2 = env_regex.Clone() env_pcre2.disable_warnings() env_pcre2["OBJSUFFIX"] = "_" + width + env_pcre2["OBJSUFFIX"] - env_pcre2.add_source_files(env.modules_sources, thirdparty_sources) env_pcre2.Append(CPPDEFINES=[("PCRE2_CODE_UNIT_WIDTH", width)]) + env_pcre2.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj pcre2_builtin("16") pcre2_builtin("32") + +# Godot source files + +module_obj = [] + env_regex.Append(CPPDEFINES=[("PCRE2_CODE_UNIT_WIDTH", 0)]) -env_regex.add_source_files(env.modules_sources, "*.cpp") +env_regex.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/squish/SCsub b/modules/squish/SCsub index b31032403f..c9e29911d8 100644 --- a/modules/squish/SCsub +++ b/modules/squish/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_squish = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + if env["builtin_squish"]: thirdparty_dir = "#thirdparty/squish/" thirdparty_sources = [ @@ -26,7 +29,16 @@ if env["builtin_squish"]: env_thirdparty = env_squish.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + # Godot source files -env_squish.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_squish.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/stb_vorbis/SCsub b/modules/stb_vorbis/SCsub index 266c87c802..8fddb23dc8 100644 --- a/modules/stb_vorbis/SCsub +++ b/modules/stb_vorbis/SCsub @@ -6,11 +6,22 @@ Import("env_modules") env_stb_vorbis = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + thirdparty_sources = ["#thirdparty/misc/stb_vorbis.c"] env_thirdparty = env_stb_vorbis.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot source files + +module_obj = [] + +env_stb_vorbis.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -# Godot's own source files -env_stb_vorbis.add_source_files(env.modules_sources, "*.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/svg/SCsub b/modules/svg/SCsub index 0bfba34fe5..c7228a8d0b 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_svg = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + thirdparty_dir = "#thirdparty/nanosvg/" thirdparty_sources = [ "nanosvg.cc", @@ -16,7 +19,15 @@ env_svg.Prepend(CPPPATH=[thirdparty_dir]) env_thirdparty = env_svg.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot source files + +module_obj = [] + +env_svg.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -# Godot's own source files -env_svg.add_source_files(env.modules_sources, "*.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 7403b01a4c..3589c8546d 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -35,10 +35,14 @@ def make_icu_data(target, source, env): g.write("#endif") +# Thirdparty source files + +thirdparty_obj = [] + if env["builtin_harfbuzz"]: env_harfbuzz = env_modules.Clone() + env_harfbuzz.disable_warnings() - # Thirdparty source files thirdparty_dir = "#thirdparty/harfbuzz/" thirdparty_sources = [ "src/hb-aat-layout.cc", @@ -107,6 +111,15 @@ if env["builtin_harfbuzz"]: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + env_harfbuzz.Append( + CPPPATH=[ + "#thirdparty/harfbuzz/src", + "#thirdparty/freetype/include", + "#thirdparty/graphite/include", + "#thirdparty/icu4c/common/", + ] + ) + if env["platform"] == "android" or env["platform"] == "linuxbsd" or env["platform"] == "server": env_harfbuzz.Append(CCFLAGS=["-DHAVE_PTHREAD"]) @@ -117,14 +130,6 @@ if env["builtin_harfbuzz"]: env_harfbuzz.Append(CCFLAGS=["-DHB_NO_MT"]) env_harfbuzz.Append( - CPPPATH=[ - "#thirdparty/harfbuzz/src", - "#thirdparty/freetype/include", - "#thirdparty/graphite/include", - "#thirdparty/icu4c/common/", - ] - ) - env_harfbuzz.Append( CCFLAGS=[ "-DHAVE_ICU_BUILTIN", "-DHAVE_ICU", @@ -133,10 +138,9 @@ if env["builtin_harfbuzz"]: "-DGRAPHITE2_STATIC", ] ) - env_harfbuzz.disable_warnings() - env_thirdparty = env_harfbuzz.Clone() - env_thirdparty.disable_warnings() - lib = env_thirdparty.add_library("harfbuzz_builtin", thirdparty_sources) + + lib = env_harfbuzz.add_library("harfbuzz_builtin", thirdparty_sources) + thirdparty_obj += lib # Needs to be appended to arrive after libscene in the linker call, # but we don't want it to arrive *after* system libs, so manual hack @@ -151,10 +155,11 @@ if env["builtin_harfbuzz"]: if not inserted: env.Append(LIBS=[lib]) + if env["builtin_graphite"]: env_graphite = env_modules.Clone() + env_graphite.disable_warnings() - # Thirdparty source files thirdparty_dir = "#thirdparty/graphite/" thirdparty_sources = [ "src/gr_char_info.cpp", @@ -203,10 +208,9 @@ if env["builtin_graphite"]: "-DGRAPHITE2_NFILEFACE", ] ) - env_graphite.disable_warnings() - env_thirdparty = env_graphite.Clone() - env_thirdparty.disable_warnings() - lib = env_thirdparty.add_library("graphite_builtin", thirdparty_sources) + + lib = env_graphite.add_library("graphite_builtin", thirdparty_sources) + thirdparty_obj += lib # Needs to be appended to arrive after libscene in the linker call, # but we don't want it to arrive *after* system libs, so manual hack @@ -221,12 +225,12 @@ if env["builtin_graphite"]: if not inserted: env.Append(LIBS=[lib]) + if env["builtin_icu"]: env_icu = env_modules.Clone() + env_icu.disable_warnings() - # Thirdparty source files thirdparty_dir = "#thirdparty/icu4c/" - # Thirdparty source files thirdparty_sources = [ "common/appendable.cpp", "common/bmpset.cpp", @@ -457,10 +461,8 @@ if env["builtin_icu"]: ] ) - env_icu.disable_warnings() - env_thirdparty = env_icu.Clone() - env_thirdparty.disable_warnings() - lib = env_thirdparty.add_library("icu_builtin", thirdparty_sources) + lib = env_icu.add_library("icu_builtin", thirdparty_sources) + thirdparty_obj += lib # Needs to be appended to arrive after libscene in the linker call, # but we don't want it to arrive *after* system libs, so manual hack @@ -475,6 +477,11 @@ if env["builtin_icu"]: if not inserted: env.Append(LIBS=[lib]) + +# Godot source files + +module_obj = [] + if env_text_server_adv["tools"]: env_text_server_adv.Append(CXXFLAGS=["-DICU_STATIC_DATA"]) @@ -486,4 +493,9 @@ env_text_server_adv.Append( "#thirdparty/icu4c/common/", ] ) -env_text_server_adv.add_source_files(env.modules_sources, "*.cpp") + +env_text_server_adv.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/text_server_adv/bitmap_font_adv.cpp b/modules/text_server_adv/bitmap_font_adv.cpp index f743000bc1..b905b7dabb 100644 --- a/modules/text_server_adv/bitmap_font_adv.cpp +++ b/modules/text_server_adv/bitmap_font_adv.cpp @@ -390,7 +390,6 @@ Error BitmapFontDataAdvanced::load_from_memory(const uint8_t *p_data, size_t p_s chr.rect.position.y = c[2]; chr.rect.size.x = c[3]; chr.rect.size.y = c[4]; - chr.texture_idx = 0; if (c[7] < 0) { chr.advance.x = c[3]; } else { diff --git a/modules/text_server_adv/dynamic_font_adv.cpp b/modules/text_server_adv/dynamic_font_adv.cpp index a081890837..af14138c6a 100644 --- a/modules/text_server_adv/dynamic_font_adv.cpp +++ b/modules/text_server_adv/dynamic_font_adv.cpp @@ -32,6 +32,7 @@ #include FT_STROKER_H #include FT_ADVANCES_H +#include FT_MULTIPLE_MASTERS_H DynamicFontDataAdvanced::DataAtSize *DynamicFontDataAdvanced::get_data_for_size(int p_size, int p_outline_size) { ERR_FAIL_COND_V(!valid, nullptr); @@ -54,7 +55,7 @@ DynamicFontDataAdvanced::DataAtSize *DynamicFontDataAdvanced::get_data_for_size( fds = E->get(); } else { if (font_mem == nullptr && font_path != String()) { - if (!font_mem_cache.empty()) { + if (!font_mem_cache.is_empty()) { font_mem = font_mem_cache.ptr(); font_mem_size = font_mem_cache.size(); } else { @@ -134,16 +135,91 @@ DynamicFontDataAdvanced::DataAtSize *DynamicFontDataAdvanced::get_data_for_size( memdelete(fds); ERR_FAIL_V_MSG(nullptr, "Error loading HB font."); } + if (p_outline_size != 0) { size_cache_outline[id] = fds; } else { size_cache[id] = fds; } - } + // Write variations. + if (fds->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + FT_MM_Var *amaster; + + FT_Get_MM_Var(fds->face, &amaster); + + Vector<hb_variation_t> hb_vars; + Vector<FT_Fixed> coords; + coords.resize(amaster->num_axis); + + FT_Get_Var_Design_Coordinates(fds->face, coords.size(), coords.ptrw()); + + for (FT_UInt i = 0; i < amaster->num_axis; i++) { + hb_variation_t var; + + // Reset to default. + var.tag = amaster->axis[i].tag; + var.value = (double)amaster->axis[i].def / 65536.f; + coords.write[i] = amaster->axis[i].def; + + if (variations.has(var.tag)) { + var.value = variations[var.tag]; + coords.write[i] = CLAMP(variations[var.tag] * 65536.f, amaster->axis[i].minimum, amaster->axis[i].maximum); + } + + hb_vars.push_back(var); + } + + FT_Set_Var_Design_Coordinates(fds->face, coords.size(), coords.ptrw()); + hb_font_set_variations(fds->hb_handle, hb_vars.is_empty() ? nullptr : &hb_vars[0], hb_vars.size()); + + FT_Done_MM_Var(library, amaster); + } + } return fds; } +Dictionary DynamicFontDataAdvanced::get_variation_list() const { + _THREAD_SAFE_METHOD_ + DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(base_size); + if (fds == nullptr) { + return Dictionary(); + } + + Dictionary ret; + // Read variations. + if (fds->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + FT_MM_Var *amaster; + + FT_Get_MM_Var(fds->face, &amaster); + + for (FT_UInt i = 0; i < amaster->num_axis; i++) { + ret[(int32_t)amaster->axis[i].tag] = Vector3i(amaster->axis[i].minimum / 65536, amaster->axis[i].maximum / 65536, amaster->axis[i].def / 65536); + } + + FT_Done_MM_Var(library, amaster); + } + return ret; +} + +void DynamicFontDataAdvanced::set_variation(const String &p_name, double p_value) { + _THREAD_SAFE_METHOD_ + int32_t tag = TS->name_to_tag(p_name); + if (!variations.has(tag) || (variations[tag] != p_value)) { + variations[tag] = p_value; + clear_cache(); + } +} + +double DynamicFontDataAdvanced::get_variation(const String &p_name) const { + _THREAD_SAFE_METHOD_ + int32_t tag = TS->name_to_tag(p_name); + if (!variations.has(tag)) { + return 0.f; + } + return variations[tag]; +} + Dictionary DynamicFontDataAdvanced::get_feature_list() const { _THREAD_SAFE_METHOD_ DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(base_size); @@ -178,8 +254,6 @@ Dictionary DynamicFontDataAdvanced::get_feature_list() const { DynamicFontDataAdvanced::TexturePosition DynamicFontDataAdvanced::find_texture_pos_for_glyph(DynamicFontDataAdvanced::DataAtSize *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) { TexturePosition ret; ret.index = -1; - ret.x = 0; - ret.y = 0; int mw = p_width; int mh = p_height; @@ -358,8 +432,8 @@ DynamicFontDataAdvanced::Character DynamicFontDataAdvanced::bitmap_to_character( } Character chr; - chr.align = Vector2(xofs, -yofs) * p_data->scale_color_font / oversampling; - chr.advance = advance * p_data->scale_color_font / oversampling; + chr.align = (Vector2(xofs, -yofs) * p_data->scale_color_font / oversampling).round(); + chr.advance = (advance * p_data->scale_color_font / oversampling).round(); chr.texture_idx = tex_pos.index; chr.found = true; diff --git a/modules/text_server_adv/dynamic_font_adv.h b/modules/text_server_adv/dynamic_font_adv.h index 4ba120f203..f9d6735c32 100644 --- a/modules/text_server_adv/dynamic_font_adv.h +++ b/modules/text_server_adv/dynamic_font_adv.h @@ -118,6 +118,8 @@ private: String font_path; Vector<uint8_t> font_mem_cache; + Map<int32_t, double> variations; + float rect_margin = 1.f; int base_size = 16; float oversampling = 1.f; @@ -146,6 +148,10 @@ public: virtual float get_descent(int p_size) const override; virtual Dictionary get_feature_list() const override; + virtual Dictionary get_variation_list() const override; + + virtual void set_variation(const String &p_name, double p_value) override; + virtual double get_variation(const String &p_name) const override; virtual float get_underline_position(int p_size) const override; virtual float get_underline_thickness(int p_size) const override; diff --git a/modules/text_server_adv/font_adv.h b/modules/text_server_adv/font_adv.h index 232d6d7d08..88b327f57b 100644 --- a/modules/text_server_adv/font_adv.h +++ b/modules/text_server_adv/font_adv.h @@ -50,6 +50,10 @@ struct FontDataAdvanced { virtual float get_descent(int p_size) const = 0; virtual Dictionary get_feature_list() const { return Dictionary(); }; + virtual Dictionary get_variation_list() const { return Dictionary(); }; + + virtual void set_variation(const String &p_name, double p_value){}; + virtual double get_variation(const String &p_name) const { return 0; }; virtual float get_underline_position(int p_size) const = 0; virtual float get_underline_thickness(int p_size) const = 0; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index dacf649987..671c2fa029 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -132,7 +132,7 @@ _FORCE_INLINE_ bool is_linebreak(char32_t p_char) { /*************************************************************************/ String TextServerAdvanced::interface_name = "ICU / HarfBuzz / Graphite"; -uint32_t TextServerAdvanced::interface_features = FEATURE_BIDI_LAYOUT | FEATURE_VERTICAL_LAYOUT | FEATURE_SHAPING | FEATURE_KASHIDA_JUSTIFICATION | FEATURE_BREAK_ITERATORS | FEATURE_USE_SUPPORT_DATA; +uint32_t TextServerAdvanced::interface_features = FEATURE_BIDI_LAYOUT | FEATURE_VERTICAL_LAYOUT | FEATURE_SHAPING | FEATURE_KASHIDA_JUSTIFICATION | FEATURE_BREAK_ITERATORS | FEATURE_USE_SUPPORT_DATA | FEATURE_FONT_VARIABLE; bool TextServerAdvanced::has_feature(Feature p_feature) { return (interface_features & p_feature) == p_feature; @@ -171,7 +171,7 @@ bool TextServerAdvanced::load_support_data(const String &p_filename) { } #else if (icu_data == nullptr) { - String filename = (p_filename.empty()) ? String("res://") + _MKSTR(ICU_DATA_NAME) : p_filename; + String filename = (p_filename.is_empty()) ? String("res://") + _MKSTR(ICU_DATA_NAME) : p_filename; FileAccess *f = FileAccess::open(filename, FileAccess::READ); if (!f) { @@ -622,6 +622,27 @@ bool TextServerAdvanced::font_get_antialiased(RID p_font) const { return fd->get_antialiased(); } +Dictionary TextServerAdvanced::font_get_variation_list(RID p_font) const { + _THREAD_SAFE_METHOD_ + const FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND_V(!fd, Dictionary()); + return fd->get_variation_list(); +} + +void TextServerAdvanced::font_set_variation(RID p_font, const String &p_name, double p_value) { + _THREAD_SAFE_METHOD_ + FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->set_variation(p_name, p_value); +} + +double TextServerAdvanced::font_get_variation(RID p_font, const String &p_name) const { + _THREAD_SAFE_METHOD_ + const FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND_V(!fd, 0); + return fd->get_variation(p_name); +} + void TextServerAdvanced::font_set_distance_field_hint(RID p_font, bool p_distance_field) { _THREAD_SAFE_METHOD_ FontDataAdvanced *fd = font_owner.getornull(p_font); @@ -1040,7 +1061,7 @@ bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_te ERR_FAIL_COND_V(!sd, false); ERR_FAIL_COND_V(p_size <= 0, false); - if (p_text.empty()) { + if (p_text.is_empty()) { return true; } @@ -1624,19 +1645,23 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { if (c == 0x0009 || c == 0x000b) { sd_glyphs[i].flags |= GRAPHEME_IS_TAB; } + if (is_whitespace(c)) { + sd_glyphs[i].flags |= GRAPHEME_IS_SPACE; + } + if (u_ispunct(c)) { + sd_glyphs[i].flags |= GRAPHEME_IS_PUNCTUATION; + } if (breaks.has(sd->glyphs[i].start)) { if (breaks[sd->glyphs[i].start]) { sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD; } else { if (is_whitespace(c)) { sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT; - sd_glyphs[i].flags |= GRAPHEME_IS_SPACE; } else { TextServer::Glyph gl; gl.start = sd_glyphs[i].start; gl.end = sd_glyphs[i].end; gl.count = 1; - gl.repeat = 1; gl.font_rid = sd_glyphs[i].font_rid; gl.font_size = sd_glyphs[i].font_size; gl.flags = GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL; @@ -1746,6 +1771,10 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { shaped_text_update_breaks(p_shaped); } + if (sd->justification_ops_valid) { + return true; // Noting to do. + } + const UChar *data = sd->utf16.ptr(); int32_t data_size = sd->utf16.length(); @@ -1776,9 +1805,9 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) { int i = _convert_pos(sd, ubrk_current(bi)); jstops[i + sd->start] = false; - int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start; + int ks = _generate_kashida_justification_opportunies(sd->text, limit, i); if (ks != -1) { - jstops[ks] = true; + jstops[ks + sd->start] = true; } limit = i; } @@ -1825,7 +1854,6 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { gl.start = sd->glyphs[i].start; gl.end = sd->glyphs[i].end; gl.count = 1; - gl.repeat = 1; gl.font_rid = sd->glyphs[i].font_rid; gl.font_size = sd->glyphs[i].font_size; gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_VIRTUAL; @@ -1860,8 +1888,6 @@ TextServer::Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced // Process glyphs. TextServer::Glyph gl; - gl.start = -1; - gl.end = -1; if (p_direction == HB_DIRECTION_RTL || p_direction == HB_DIRECTION_BTT) { gl.flags |= TextServer::GRAPHEME_IS_RTL; @@ -1907,7 +1933,6 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star gl.index = p_sd->text[i]; gl.font_size = fs; gl.font_rid = RID(); - gl.flags = 0; if (p_direction == HB_DIRECTION_RTL || p_direction == HB_DIRECTION_BTT) { gl.flags |= TextServer::GRAPHEME_IS_RTL; } @@ -1959,7 +1984,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star ftrs.push_back(feature); } } - hb_shape(hb_font, p_sd->hb_buffer, ftrs.empty() ? nullptr : &ftrs[0], ftrs.size()); + hb_shape(hb_font, p_sd->hb_buffer, ftrs.is_empty() ? nullptr : &ftrs[0], ftrs.size()); unsigned int glyph_count = 0; hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(p_sd->hb_buffer, &glyph_count); @@ -2106,7 +2131,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { sd->script_iter = memnew(ScriptIterator(sd->text, 0, sd->text.length())); } - if (sd->bidi_override.empty()) { + if (sd->bidi_override.is_empty()) { sd->bidi_override.push_back(Vector2i(0, sd->end)); } @@ -2232,7 +2257,6 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { gl.start = span.start; gl.end = span.end; gl.count = 1; - gl.index = 0; gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL; if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = sd->objects[span.embedded_key].rect.size.x; diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index f26b87f67e..8c26554158 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -138,6 +138,10 @@ public: virtual bool font_get_antialiased(RID p_font) const override; virtual Dictionary font_get_feature_list(RID p_font) const override; + virtual Dictionary font_get_variation_list(RID p_font) const override; + + virtual void font_set_variation(RID p_font, const String &p_name, double p_value) override; + virtual double font_get_variation(RID p_font, const String &p_name) const override; virtual void font_set_hinting(RID p_font, Hinting p_hinting) override; virtual Hinting font_get_hinting(RID p_font) const override; diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub index 7650e27063..03eccbe7bd 100644 --- a/modules/text_server_fb/SCsub +++ b/modules/text_server_fb/SCsub @@ -9,4 +9,5 @@ env_text_server_fb.Append( "#thirdparty/freetype/include", ] ) + env_text_server_fb.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/text_server_fb/bitmap_font_fb.cpp b/modules/text_server_fb/bitmap_font_fb.cpp index 5ab8edbd71..99cbccb69a 100644 --- a/modules/text_server_fb/bitmap_font_fb.cpp +++ b/modules/text_server_fb/bitmap_font_fb.cpp @@ -198,7 +198,6 @@ Error BitmapFontDataFallback::load_from_memory(const uint8_t *p_data, size_t p_s chr.rect.position.y = c[2]; chr.rect.size.x = c[3]; chr.rect.size.y = c[4]; - chr.texture_idx = 0; if (c[7] < 0) { chr.advance.x = c[3]; } else { diff --git a/modules/text_server_fb/dynamic_font_fb.cpp b/modules/text_server_fb/dynamic_font_fb.cpp index fbf9574a78..ca9e5b580b 100644 --- a/modules/text_server_fb/dynamic_font_fb.cpp +++ b/modules/text_server_fb/dynamic_font_fb.cpp @@ -139,8 +139,6 @@ DynamicFontDataFallback::DataAtSize *DynamicFontDataFallback::get_data_for_size( DynamicFontDataFallback::TexturePosition DynamicFontDataFallback::find_texture_pos_for_glyph(DynamicFontDataFallback::DataAtSize *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) { TexturePosition ret; ret.index = -1; - ret.x = 0; - ret.y = 0; int mw = p_width; int mh = p_height; @@ -319,8 +317,8 @@ DynamicFontDataFallback::Character DynamicFontDataFallback::bitmap_to_character( } Character chr; - chr.align = Vector2(xofs, -yofs) * p_data->scale_color_font / oversampling; - chr.advance = advance * p_data->scale_color_font / oversampling; + chr.align = (Vector2(xofs, -yofs) * p_data->scale_color_font / oversampling).round(); + chr.advance = (advance * p_data->scale_color_font / oversampling).round(); chr.texture_idx = tex_pos.index; chr.found = true; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 383250996d..675d0e5d4d 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -45,6 +45,10 @@ _FORCE_INLINE_ bool is_linebreak(char32_t p_char) { return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029); } +_FORCE_INLINE_ bool is_punct(char32_t p_char) { + return (p_char >= 0x0020 && p_char <= 0x002F) || (p_char >= 0x003A && p_char <= 0x0040) || (p_char >= 0x005B && p_char <= 0x0060) || (p_char >= 0x007B && p_char <= 0x007E) || (p_char >= 0x2000 && p_char <= 0x206F) || (p_char >= 0x3000 && p_char <= 0x303F); +} + /*************************************************************************/ String TextServerFallback::interface_name = "Fallback"; @@ -1029,6 +1033,9 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { for (int i = 0; i < sd_size; i++) { if (sd->glyphs[i].count > 0) { char32_t c = sd->text[sd->glyphs[i].start]; + if (is_punct(c)) { + sd->glyphs.write[i].flags |= GRAPHEME_IS_PUNCTUATION; + } if (is_whitespace(c) && !is_linebreak(c)) { sd->glyphs.write[i].flags |= GRAPHEME_IS_SPACE; sd->glyphs.write[i].flags |= GRAPHEME_IS_BREAK_SOFT; diff --git a/modules/theora/SCsub b/modules/theora/SCsub index a01e65b4b0..6038ea086a 100644 --- a/modules/theora/SCsub +++ b/modules/theora/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_theora = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + if env["builtin_libtheora"]: thirdparty_dir = "#thirdparty/libtheora/" thirdparty_sources = [ @@ -80,7 +83,16 @@ if env["builtin_libtheora"]: env_thirdparty = env_theora.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + # Godot source files -env_theora.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_theora.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/tinyexr/SCsub b/modules/tinyexr/SCsub index 84b3b4015b..30bde96fb4 100644 --- a/modules/tinyexr/SCsub +++ b/modules/tinyexr/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_tinyexr = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + # Not unbundled for now as they are not commonly available as shared library thirdparty_dir = "#thirdparty/tinyexr/" thirdparty_sources = [ @@ -20,7 +23,15 @@ env_tinyexr.Append(CPPDEFINES=["TINYEXR_USE_THREAD"]) env_thirdparty = env_tinyexr.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot source files + +module_obj = [] + +env_tinyexr.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -# Godot's own source files -env_tinyexr.add_source_files(env.modules_sources, "*.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/upnp/SCsub b/modules/upnp/SCsub index 2e129e15ca..bc0b215be3 100644 --- a/modules/upnp/SCsub +++ b/modules/upnp/SCsub @@ -7,6 +7,8 @@ env_upnp = env_modules.Clone() # Thirdparty source files +thirdparty_obj = [] + if env["builtin_miniupnpc"]: thirdparty_dir = "#thirdparty/miniupnpc/" thirdparty_sources = [ @@ -31,7 +33,16 @@ if env["builtin_miniupnpc"]: env_thirdparty = env_upnp.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + # Godot source files -env_upnp.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_upnp.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp index 988bf3017d..3c9bf9a4b4 100644 --- a/modules/upnp/upnp.cpp +++ b/modules/upnp/upnp.cpp @@ -36,7 +36,7 @@ #include <stdlib.h> bool UPNP::is_common_device(const String &dev) const { - return dev.empty() || + return dev.is_empty() || dev.find("InternetGatewayDevice") >= 0 || dev.find("WANIPConnection") >= 0 || dev.find("WANPPPConnection") >= 0 || @@ -76,7 +76,7 @@ int UPNP::discover(int timeout, int ttl, const String &device_filter) { struct UPNPDev *dev = devlist; while (dev) { - if (device_filter.empty() || strstr(dev->st, device_filter.utf8().get_data())) { + if (device_filter.is_empty() || strstr(dev->st, device_filter.utf8().get_data())) { add_device_to_list(dev, devlist); } diff --git a/modules/upnp/upnp_device.cpp b/modules/upnp/upnp_device.cpp index 40eb6106a0..34af6898be 100644 --- a/modules/upnp/upnp_device.cpp +++ b/modules/upnp/upnp_device.cpp @@ -65,7 +65,7 @@ int UPNPDevice::add_port_mapping(int port, int port_internal, String desc, Strin itos(port).utf8().get_data(), itos(port_internal).utf8().get_data(), igd_our_addr.utf8().get_data(), - desc.empty() ? nullptr : desc.utf8().get_data(), + desc.is_empty() ? nullptr : desc.utf8().get_data(), proto.utf8().get_data(), nullptr, // Remote host, always nullptr as IGDs don't support it duration > 0 ? itos(duration).utf8().get_data() : nullptr); diff --git a/modules/vhacd/SCsub b/modules/vhacd/SCsub index ecd432b275..1ff4122114 100644 --- a/modules/vhacd/SCsub +++ b/modules/vhacd/SCsub @@ -7,6 +7,8 @@ env_vhacd = env_modules.Clone() # Thirdparty source files +thirdparty_obj = [] + thirdparty_dir = "#thirdparty/vhacd/" thirdparty_sources = [ @@ -24,10 +26,19 @@ thirdparty_sources = [ thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env_vhacd.Prepend(CPPPATH=[thirdparty_dir + "/inc"]) +env_vhacd.Prepend(CPPPATH=[thirdparty_dir + "inc"]) env_thirdparty = env_vhacd.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot source files + +module_obj = [] + +env_vhacd.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -env_vhacd.add_source_files(env.modules_sources, "*.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index f9ef184579..18a104ffb0 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -810,7 +810,7 @@ void VisualScript::get_custom_signal_list(List<StringName> *r_custom_signals) co int VisualScript::get_available_id() const { int max_id = 0; for (Map<StringName, Function>::Element *E = functions.front(); E; E = E->next()) { - if (E->get().nodes.empty()) { + if (E->get().nodes.is_empty()) { continue; } @@ -1401,7 +1401,7 @@ Set<int> VisualScript::get_output_sequence_ports_connected(const String &edited_ } VisualScript::~VisualScript() { - while (!functions.empty()) { + while (!functions.is_empty()) { remove_function(functions.front()->key()); } } @@ -1506,7 +1506,7 @@ void VisualScriptInstance::_dependency_step(VisualScriptNodeInstance *node, int pass_stack[node->pass_idx] = p_pass; - if (!node->dependencies.empty()) { + if (!node->dependencies.is_empty()) { int dc = node->dependencies.size(); VisualScriptNodeInstance **deps = node->dependencies.ptrw(); @@ -1593,7 +1593,7 @@ Variant VisualScriptInstance::_call_internal(const StringName &p_method, void *p } else { //run dependencies first - if (!node->dependencies.empty()) { + if (!node->dependencies.is_empty()) { int dc = node->dependencies.size(); VisualScriptNodeInstance **deps = node->dependencies.ptrw(); diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 64abd3dd84..91a258d412 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -672,7 +672,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { GraphNode *gnode = memnew(GraphNode); gnode->set_title(node->get_caption()); - gnode->set_offset(pos * EDSCALE); + gnode->set_position_offset(pos * EDSCALE); if (error_line == E->get()) { gnode->set_overlay(GraphNode::OVERLAY_POSITION); } else if (node->is_breakpoint()) { @@ -724,7 +724,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { line_edit->connect("text_changed", callable_mp(this, &VisualScriptEditor::_expression_text_changed), varray(E->get())); } else { String text = node->get_text(); - if (!text.empty()) { + if (!text.is_empty()) { has_gnode_text = true; Label *label = memnew(Label); label->set_text(text); @@ -1723,7 +1723,7 @@ void VisualScriptEditor::_on_nodes_delete() { } } - if (to_erase.empty()) { + if (to_erase.is_empty()) { return; } @@ -1776,7 +1776,7 @@ void VisualScriptEditor::_on_nodes_duplicate() { } } - if (to_duplicate.empty()) { + if (to_duplicate.is_empty()) { return; } @@ -2547,7 +2547,7 @@ void VisualScriptEditor::set_edited_resource(const RES &p_res) { } _update_graph(); - _update_members(); + call_deferred("_update_members"); } void VisualScriptEditor::enable_editor() { @@ -2566,7 +2566,7 @@ String VisualScriptEditor::get_name() { if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) { name = script->get_path().get_file(); if (is_unsaved()) { - if (script->get_path().empty()) { + if (script->get_path().is_empty()) { name = TTR("[unsaved]"); } name += "(*)"; @@ -2588,7 +2588,7 @@ bool VisualScriptEditor::is_unsaved() { bool unsaved = script->is_edited() || script->are_subnodes_edited() || - script->get_path().empty(); // In memory. + script->get_path().is_empty(); // In memory. return unsaved; } @@ -2639,7 +2639,7 @@ void VisualScriptEditor::_center_on_node(const StringName &p_func, int p_id) { if (gn) { gn->set_selected(true); - Vector2 new_scroll = gn->get_offset() - graph->get_size() * 0.5 + gn->get_size() * 0.5; + Vector2 new_scroll = gn->get_position_offset() - graph->get_size() * 0.5 + gn->get_size() * 0.5; graph->set_scroll_ofs(new_scroll); script->set_function_scroll(p_func, new_scroll / EDSCALE); script->set_edited(true); @@ -2852,7 +2852,7 @@ void VisualScriptEditor::_move_node(const StringName &p_func, int p_id, const Ve Node *node = graph->get_node(itos(p_id)); if (Object::cast_to<GraphNode>(node)) { - Object::cast_to<GraphNode>(node)->set_offset(p_to); + Object::cast_to<GraphNode>(node)->set_position_offset(p_to); } script->set_node_position(p_func, p_id, p_to / EDSCALE); @@ -4183,7 +4183,7 @@ void VisualScriptEditor::_menu_option(int p_what) { } } - if (clipboard->nodes.empty()) { + if (clipboard->nodes.is_empty()) { break; } @@ -4218,7 +4218,7 @@ void VisualScriptEditor::_menu_option(int p_what) { break; } - if (clipboard->nodes.empty()) { + if (clipboard->nodes.is_empty()) { EditorNode::get_singleton()->show_warning(TTR("Clipboard is empty!")); break; } @@ -4786,7 +4786,7 @@ VisualScriptEditor::VisualScriptEditor() { graph = memnew(GraphEdit); add_child(graph); graph->set_v_size_flags(Control::SIZE_EXPAND_FILL); - graph->set_anchors_and_margins_preset(Control::PRESET_WIDE); + graph->set_anchors_and_offsets_preset(Control::PRESET_WIDE); graph->connect("node_selected", callable_mp(this, &VisualScriptEditor::_node_selected)); graph->connect("begin_node_move", callable_mp(this, &VisualScriptEditor::_begin_node_move)); graph->connect("end_node_move", callable_mp(this, &VisualScriptEditor::_end_node_move)); @@ -4858,8 +4858,8 @@ VisualScriptEditor::VisualScriptEditor() { function_create_dialog = memnew(ConfirmationDialog); function_create_dialog->set_title(TTR("Create Function")); function_create_dialog->add_child(function_vb); - function_create_dialog->get_ok()->set_text(TTR("Create")); - function_create_dialog->get_ok()->connect("pressed", callable_mp(this, &VisualScriptEditor::_create_function)); + function_create_dialog->get_ok_button()->set_text(TTR("Create")); + function_create_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VisualScriptEditor::_create_function)); add_child(function_create_dialog); select_func_text = memnew(Label); @@ -4870,9 +4870,9 @@ VisualScriptEditor::VisualScriptEditor() { add_child(select_func_text); hint_text = memnew(Label); - hint_text->set_anchor_and_margin(MARGIN_TOP, ANCHOR_END, -100); - hint_text->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, 0); - hint_text->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, 0); + hint_text->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -100); + hint_text->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); + hint_text->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); hint_text->set_align(Label::ALIGN_CENTER); hint_text->set_valign(Label::VALIGN_CENTER); graph->add_child(hint_text); @@ -4902,7 +4902,7 @@ VisualScriptEditor::VisualScriptEditor() { graph->connect("connection_to_empty", callable_mp(this, &VisualScriptEditor::_graph_connect_to_empty)); edit_signal_dialog = memnew(AcceptDialog); - edit_signal_dialog->get_ok()->set_text(TTR("Close")); + edit_signal_dialog->get_ok_button()->set_text(TTR("Close")); add_child(edit_signal_dialog); signal_editor = memnew(VisualScriptEditorSignalEdit); @@ -4912,7 +4912,7 @@ VisualScriptEditor::VisualScriptEditor() { edit_signal_edit->edit(signal_editor); edit_variable_dialog = memnew(AcceptDialog); - edit_variable_dialog->get_ok()->set_text(TTR("Close")); + edit_variable_dialog->get_ok_button()->set_text(TTR("Close")); add_child(edit_variable_dialog); variable_editor = memnew(VisualScriptEditorVariableEdit); @@ -4944,7 +4944,7 @@ VisualScriptEditor::VisualScriptEditor() { new_connect_node_select = memnew(VisualScriptPropertySelector); add_child(new_connect_node_select); new_connect_node_select->connect("selected", callable_mp(this, &VisualScriptEditor::_selected_connect_node)); - new_connect_node_select->get_cancel()->connect("pressed", callable_mp(this, &VisualScriptEditor::_cancel_connect_node)); + new_connect_node_select->get_cancel_button()->connect("pressed", callable_mp(this, &VisualScriptEditor::_cancel_connect_node)); new_virtual_method_select = memnew(VisualScriptPropertySelector); add_child(new_virtual_method_select); diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index 54d86d5a9c..fde6ebbecc 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -310,7 +310,7 @@ void VisualScriptPropertySelector::_update_search() { found = true; } - get_ok()->set_disabled(root->get_children() == nullptr); + get_ok_button()->set_disabled(root->get_children() == nullptr); } void VisualScriptPropertySelector::create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text) { @@ -358,7 +358,7 @@ void VisualScriptPropertySelector::get_visual_node_names(const String &root_filt continue; } - bool in_modifier = p_modifiers.empty(); + bool in_modifier = p_modifiers.is_empty(); for (Set<String>::Element *F = p_modifiers.front(); F && in_modifier; F = F->next()) { if (E->get().findn(F->get()) != -1) { in_modifier = true; @@ -705,8 +705,8 @@ VisualScriptPropertySelector::VisualScriptPropertySelector() { search_box->connect("gui_input", callable_mp(this, &VisualScriptPropertySelector::_sbox_input)); search_options = memnew(Tree); vbc->add_margin_child(TTR("Matches:"), search_options, true); - get_ok()->set_text(TTR("Open")); - get_ok()->set_disabled(true); + get_ok_button()->set_text(TTR("Open")); + get_ok_button()->set_disabled(true); register_text_enter(search_box); set_hide_on_ok(false); search_options->connect("item_activated", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); diff --git a/modules/vorbis/SCsub b/modules/vorbis/SCsub index 05d46757d3..bc31fff066 100644 --- a/modules/vorbis/SCsub +++ b/modules/vorbis/SCsub @@ -8,9 +8,10 @@ Import("env_modules") env_vorbis = env_modules.Clone() -stub = True - # Thirdparty source files + +thirdparty_obj = [] + if env["builtin_libvorbis"]: thirdparty_dir = "#thirdparty/libvorbis/" thirdparty_sources = [ @@ -51,7 +52,16 @@ if env["builtin_libvorbis"]: env_thirdparty = env_vorbis.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + + +# Godot source files + +module_obj = [] + +env_vorbis.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj -# Module files -env_vorbis.add_source_files(env.modules_sources, "register_types.cpp") +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/webm/SCsub b/modules/webm/SCsub index 247b4ead37..44e80e2870 100644 --- a/modules/webm/SCsub +++ b/modules/webm/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_webm = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + thirdparty_dir = "#thirdparty/libsimplewebm/" thirdparty_sources = [ "libwebm/mkvparser/mkvparser.cc", @@ -31,7 +34,15 @@ if env["builtin_libvpx"]: env_thirdparty = env_webm.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj # Godot source files -env_webm.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_webm.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/webm/libvpx/SCsub b/modules/webm/libvpx/SCsub index d0744fa313..67d3f1bebd 100644 --- a/modules/webm/libvpx/SCsub +++ b/modules/webm/libvpx/SCsub @@ -278,7 +278,7 @@ if webm_cpu_x86: try: yasm_found = True subprocess.Popen([yasm_path, "--version"], stdout=devnull, stderr=devnull).communicate() - except: + except Exception: yasm_found = False if yasm_found: break diff --git a/modules/webp/SCsub b/modules/webp/SCsub index 58f2bb35e6..4c0c2f7893 100644 --- a/modules/webp/SCsub +++ b/modules/webp/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_webp = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + if env["builtin_libwebp"]: thirdparty_dir = "#thirdparty/libwebp/" thirdparty_sources = [ @@ -130,7 +133,16 @@ if env["builtin_libwebp"]: env_thirdparty = env_webp.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + # Godot source files -env_webp.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_webp.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index b169687f25..99867b2d74 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -39,7 +39,7 @@ #include <webp/encode.h> static Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality) { - ERR_FAIL_COND_V(p_image.is_null() || p_image->empty(), Vector<uint8_t>()); + ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>()); Ref<Image> img = p_image->duplicate(); if (img->detect_alpha()) { diff --git a/modules/webrtc/SCsub b/modules/webrtc/SCsub index 4f870ddb2f..31b8a73bf2 100644 --- a/modules/webrtc/SCsub +++ b/modules/webrtc/SCsub @@ -3,8 +3,6 @@ Import("env") Import("env_modules") -# Thirdparty source files - env_webrtc = env_modules.Clone() use_gdnative = env_webrtc["module_gdnative_enabled"] diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub index 13e51a39c0..4c022c43cf 100644 --- a/modules/websocket/SCsub +++ b/modules/websocket/SCsub @@ -5,28 +5,44 @@ Import("env_modules") env_ws = env_modules.Clone() +thirdparty_obj = [] + if env["platform"] == "javascript": # Our JavaScript/C++ interface. env.AddJSLibraries(["library_godot_websocket.js"]) + elif env["builtin_wslay"]: # Thirdparty source files - wslay_dir = "#thirdparty/wslay/" - wslay_sources = [ + thirdparty_dir = "#thirdparty/wslay/" + thirdparty_sources = [ "wslay_net.c", "wslay_event.c", "wslay_queue.c", "wslay_stack.c", "wslay_frame.c", ] - wslay_sources = [wslay_dir + s for s in wslay_sources] - env_ws.Prepend(CPPPATH=[wslay_dir + "includes/"]) + thirdparty_sources = [thirdparty_dir + s for s in thirdparty_sources] + + env_ws.Prepend(CPPPATH=[thirdparty_dir + "includes/"]) env_ws.Append(CPPDEFINES=["HAVE_CONFIG_H"]) + if env["platform"] == "windows" or env["platform"] == "uwp": env_ws.Append(CPPDEFINES=["HAVE_WINSOCK2_H"]) else: env_ws.Append(CPPDEFINES=["HAVE_NETINET_IN_H"]) - env_wslay = env_ws.Clone() - env_wslay.disable_warnings() - env_wslay.add_source_files(env.modules_sources, wslay_sources) -env_ws.add_source_files(env.modules_sources, "*.cpp") + env_thirdparty = env_ws.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + + +# Godot source files + +module_obj = [] + +env_ws.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/xatlas_unwrap/SCsub b/modules/xatlas_unwrap/SCsub index c659349d05..aa6bdaea33 100644 --- a/modules/xatlas_unwrap/SCsub +++ b/modules/xatlas_unwrap/SCsub @@ -6,6 +6,9 @@ Import("env_modules") env_xatlas_unwrap = env_modules.Clone() # Thirdparty source files + +thirdparty_obj = [] + if env["builtin_xatlas"]: thirdparty_dir = "#thirdparty/xatlas/" thirdparty_sources = [ @@ -17,7 +20,16 @@ if env["builtin_xatlas"]: env_thirdparty = env_xatlas_unwrap.Clone() env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env.modules_sources += thirdparty_obj + # Godot source files -env_xatlas_unwrap.add_source_files(env.modules_sources, "*.cpp") + +module_obj = [] + +env_xatlas_unwrap.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) |