From ad214c03560d721d9b8bbff03835fc7fa4884943 Mon Sep 17 00:00:00 2001 From: Gordon MacPherson Date: Fri, 30 Aug 2019 02:21:40 +0100 Subject: [PATCH] Assimp FBX Import support Issues fixed: - Updated assimp to latest and backported fixes into godot. - Fixed file scale being ignored from FBX file. - Fixed bone removal - Implemented proper armature binding - Fixed recursion not always going through the entire path - Implemented assimp global scaling system - Fixed assimp global scale process to support unit conversion - Implemented proper fbx scaling - Fixed asserts caused by missing faces in some models which could crash - Fixed valid bone removal - Fixed root node being overwriten by assimp which caused data loss - Fixed armature construction so that it works with multiple roots - Implemented basic support for FBX standard materials - Refactoring to improve code quality and improve function reuse. - Simplified node creation from assimp scene into subsections: create_light, create_mesh, create_bone. - Creating meshes is now done after hierarchy is created so that the skeleton is always available. - Added support to assimp to support file scale in all formats which call SetFileScale. - Many other fixes provided into assimp. Known issues: - FBX pivots from Maya do not currently work. (workaround: for now use blender import and export to remove pivot tracks) - Hierarchy creates an extra node for each mesh - this was done intentionally but we intended to do a pass to remove these as they're a required node. - When an animated mesh has not executed any animation the rest pose is wrong. Co-authored-by: K. S. Ernest (iFire) Lee --- .../assimp/editor_scene_importer_assimp.cpp | 1691 ++++++---------- modules/assimp/editor_scene_importer_assimp.h | 145 +- modules/assimp/import_state.h | 115 ++ modules/assimp/import_utils.h | 448 +++++ thirdparty/assimp/assimp/config.h | 7 + .../assimp/code/Common/BaseImporter.cpp | 27 +- thirdparty/assimp/code/FBX/FBXConverter.cpp | 96 +- thirdparty/assimp/code/FBX/FBXConverter.h | 7 - thirdparty/assimp/code/FBX/FBXDocument.cpp | 8 - thirdparty/assimp/code/FBX/FBXExporter.cpp | 41 +- thirdparty/assimp/code/FBX/FBXImporter.cpp | 10 +- .../assimp/code/FBX/FBXMeshGeometry.cpp | 7 +- .../PostProcessing/CalcTangentsProcess.cpp | 6 +- .../code/PostProcessing/ScaleProcess.cpp | 125 +- .../assimp/code/PostProcessing/ScaleProcess.h | 13 +- .../assimp/contrib/utf8cpp/doc/ReleaseNotes | 12 + .../assimp/contrib/utf8cpp/doc/utf8cpp.html | 1789 +++++++++++++++++ .../assimp/include/assimp/.editorconfig | 8 - .../assimp/include/assimp/BaseImporter.h | 61 +- thirdparty/assimp/include/assimp/config.h.in | 9 +- .../assimp/include/assimp/irrXMLWrapper.h | 144 -- thirdparty/assimp/include/assimp/scene.h | 2 +- 22 files changed, 3267 insertions(+), 1504 deletions(-) create mode 100644 modules/assimp/import_state.h create mode 100644 modules/assimp/import_utils.h create mode 100644 thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes create mode 100644 thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html delete mode 100644 thirdparty/assimp/include/assimp/.editorconfig delete mode 100644 thirdparty/assimp/include/assimp/irrXMLWrapper.h diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index 05f9120a077..e5439fd132a 100644 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ b/modules/assimp/editor_scene_importer_assimp.cpp @@ -28,24 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "assimp/DefaultLogger.hpp" -#include "assimp/Importer.hpp" -#include "assimp/LogStream.hpp" -#include "assimp/Logger.hpp" -#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 "editor_scene_importer_assimp.h" #include "core/bind/core_bind.h" #include "core/io/image_loader.h" #include "editor/editor_file_system.h" #include "editor/import/resource_importer_scene.h" -#include "editor_scene_importer_assimp.h" #include "editor_settings.h" +#include "import_utils.h" #include "scene/3d/camera.h" #include "scene/3d/light.h" #include "scene/3d/mesh_instance.h" @@ -53,7 +42,19 @@ #include "scene/main/node.h" #include "scene/resources/material.h" #include "scene/resources/surface_tool.h" -#include "zutil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include void EditorSceneImporterAssimp::get_extensions(List *r_extensions) const { @@ -92,18 +93,6 @@ uint32_t EditorSceneImporterAssimp::get_import_flags() const { return IMPORT_SCENE; } -AssimpStream::AssimpStream() { - // empty -} - -AssimpStream::~AssimpStream() { - // empty -} - -void AssimpStream::write(const char *message) { - print_verbose(String("Open Asset Import: ") + String(message).strip_edges()); -} - void EditorSceneImporterAssimp::_bind_methods() { } @@ -122,35 +111,36 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f //} 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 | + aiProcess_FlipWindingOrder | // very important for culling so that it is done in the correct order. //aiProcess_DropNormals | //aiProcess_GenSmoothNormals | - aiProcess_JoinIdenticalVertices | + //aiProcess_JoinIdenticalVertices | aiProcess_ImproveCacheLocality | - aiProcess_LimitBoneWeights | //aiProcess_RemoveRedundantMaterials | // Causes a crash - aiProcess_SplitLargeMeshes | + //aiProcess_SplitLargeMeshes | aiProcess_Triangulate | aiProcess_GenUVCoords | //aiProcess_FindDegenerates | - aiProcess_SortByPType | - aiProcess_FindInvalidData | + //aiProcess_SortByPType | + // aiProcess_FindInvalidData | aiProcess_TransformUVCoords | aiProcess_FindInstances | //aiProcess_FixInfacingNormals | - //aiProcess_ValidateDataStructure | + aiProcess_ValidateDataStructure | aiProcess_OptimizeMeshes | //aiProcess_OptimizeGraph | //aiProcess_Debone | - aiProcess_EmbedTextures | - aiProcess_SplitByBoneCount | + // aiProcess_EmbedTextures | + //aiProcess_SplitByBoneCount | 0; - const aiScene *scene = importer.ReadFile(s_path.c_str(), - post_process_Steps); - ERR_FAIL_COND_V_MSG(scene == NULL, NULL, String("Open Asset Import failed to open: ") + String(importer.GetErrorString()) + "."); + aiScene *scene = (aiScene *)importer.ReadFile(s_path.c_str(), post_process_Steps); + ERR_EXPLAIN(String("Open Asset Import failed to open: ") + String(importer.GetErrorString())); + ERR_FAIL_COND_V(scene == NULL, NULL); return _generate_scene(p_path, scene, p_flags, p_bake_fps, max_bone_weights); } @@ -281,158 +271,7 @@ T EditorSceneImporterAssimp::_interpolate_track(const Vector &p_times, co ERR_FAIL_V(p_values[0]); } -void EditorSceneImporterAssimp::_generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map &ownership, Map &bind_xforms) { - - Transform mesh_offset = _get_global_assimp_node_transform(p_assimp_node); - //mesh_offset.basis = Basis(); - for (uint32_t i = 0; i < p_assimp_node->mNumMeshes; i++) { - const aiMesh *mesh = state.assimp_scene->mMeshes[i]; - int owned_by = -1; - for (uint32_t j = 0; j < mesh->mNumBones; j++) { - const aiBone *bone = mesh->mBones[j]; - String name = _assimp_get_string(bone->mName); - - if (ownership.has(name)) { - owned_by = ownership[name]; - break; - } - } - - if (owned_by == -1) { //no owned, create new unique id - owned_by = 1; - for (Map::Element *E = ownership.front(); E; E = E->next()) { - owned_by = MAX(E->get() + 1, owned_by); - } - } - - for (uint32_t j = 0; j < mesh->mNumBones; j++) { - const aiBone *bone = mesh->mBones[j]; - String name = _assimp_get_string(bone->mName); - ownership[name] = owned_by; - //store the actual full path for the bone transform - //when skeleton finds its place in the tree, it will be restored - bind_xforms[name] = mesh_offset * _assimp_matrix_transform(bone->mOffsetMatrix); - } - } - - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _generate_bone_groups(state, p_assimp_node->mChildren[i], ownership, bind_xforms); - } -} - -void EditorSceneImporterAssimp::_fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map &ownership, Map &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector &p_holes, const Map &bind_xforms) { - - String name = _assimp_get_string(p_assimp_node->mName); - if (name == String()) { - name = "AuxiliaryBone" + itos(holecount++); - } - - Transform pose = _assimp_matrix_transform(p_assimp_node->mTransformation); - - if (!ownership.has(name)) { - //not a bone, it's a hole - Vector holes = p_holes; - SkeletonHole hole; //add a new one - hole.name = name; - hole.pose = pose; - hole.node = p_assimp_node; - hole.parent = p_parent_name; - holes.push_back(hole); - - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _fill_node_relationships(state, p_assimp_node->mChildren[i], ownership, skeleton_map, p_skeleton_id, p_skeleton, name, holecount, holes, bind_xforms); - } - - return; - } else if (ownership[name] != p_skeleton_id) { - //oh, it's from another skeleton? fine.. reparent all bones to this skeleton. - int prev_owner = ownership[name]; - ERR_FAIL_COND_MSG(skeleton_map.has(prev_owner), "A previous skeleton exists for bone '" + name + "', this type of skeleton layout is unsupported."); - for (Map::Element *E = ownership.front(); E; E = E->next()) { - if (E->get() == prev_owner) { - E->get() = p_skeleton_id; - } - } - } - - //valid bone, first fill holes if needed - for (int i = 0; i < p_holes.size(); i++) { - - int bone_idx = p_skeleton->get_bone_count(); - p_skeleton->add_bone(p_holes[i].name); - int parent_idx = p_skeleton->find_bone(p_holes[i].parent); - if (parent_idx >= 0) { - p_skeleton->set_bone_parent(bone_idx, parent_idx); - } - - Transform pose_transform = _get_global_assimp_node_transform(p_holes[i].node); - p_skeleton->set_bone_rest(bone_idx, pose_transform); - - state.bone_owners[p_holes[i].name] = skeleton_map[p_skeleton_id]; - } - - //finally fill bone - - int bone_idx = p_skeleton->get_bone_count(); - p_skeleton->add_bone(name); - int parent_idx = p_skeleton->find_bone(p_parent_name); - if (parent_idx >= 0) { - p_skeleton->set_bone_parent(bone_idx, parent_idx); - } - //p_skeleton->set_bone_pose(bone_idx, pose); - if (bind_xforms.has(name)) { - //for now this is the full path to the bone in rest pose - //when skeleton finds it's place in the tree, it will get fixed - p_skeleton->set_bone_rest(bone_idx, bind_xforms[name]); - } - state.bone_owners[name] = skeleton_map[p_skeleton_id]; - //go to children - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _fill_node_relationships(state, p_assimp_node->mChildren[i], ownership, skeleton_map, p_skeleton_id, p_skeleton, name, holecount, Vector(), bind_xforms); - } -} - -void EditorSceneImporterAssimp::_generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map &ownership, Map &skeleton_map, const Map &bind_xforms) { - - //find skeletons at this level, there may be multiple root nodes for each - Map > skeletons_found; - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - String name = _assimp_get_string(p_assimp_node->mChildren[i]->mName); - if (ownership.has(name)) { - int skeleton = ownership[name]; - if (!skeletons_found.has(skeleton)) { - skeletons_found[skeleton] = List(); - } - skeletons_found[skeleton].push_back(p_assimp_node->mChildren[i]); - } - } - - //go via the potential skeletons found and generate the actual skeleton - for (Map >::Element *E = skeletons_found.front(); E; E = E->next()) { - ERR_CONTINUE(skeleton_map.has(E->key())); //skeleton already exists? this can't be.. skip - Skeleton *skeleton = memnew(Skeleton); - //this the only way to reliably use multiple meshes with one skeleton, at the cost of less precision - skeleton->set_use_bones_in_world_transform(true); - skeleton_map[E->key()] = state.skeletons.size(); - state.skeletons.push_back(skeleton); - int holecount = 1; - //fill the bones and their relationships - for (List::Element *F = E->get().front(); F; F = F->next()) { - _fill_node_relationships(state, F->get(), ownership, skeleton_map, E->key(), skeleton, "", holecount, Vector(), bind_xforms); - } - } - - //go to the children - for (uint32_t i = 0; i < p_assimp_node->mNumChildren; i++) { - String name = _assimp_get_string(p_assimp_node->mChildren[i]->mName); - if (ownership.has(name)) { - continue; //a bone, so don't bother with this - } - _generate_skeletons(state, p_assimp_node->mChildren[i], ownership, skeleton_map, bind_xforms); - } -} - -Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) { +Spatial *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 == NULL, NULL); ImportState state; @@ -443,60 +282,37 @@ Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const state.fbx = false; state.animation_player = NULL; - real_t scale_factor = 1.0f; - { - //handle scale - String ext = p_path.get_file().get_extension().to_lower(); - if (ext == "fbx") { - if (scene->mMetaData != NULL) { - float factor = 1.0; - scene->mMetaData->Get("UnitScaleFactor", factor); - scale_factor = factor * 0.01f; - } - state.fbx = true; - } - } - - state.root->set_scale(Vector3(scale_factor, scale_factor, scale_factor)); - //fill light map cache for (size_t l = 0; l < scene->mNumLights; l++) { aiLight *ai_light = scene->mLights[l]; ERR_CONTINUE(ai_light == NULL); - state.light_cache[_assimp_get_string(ai_light->mName)] = l; + state.light_cache[AssimpUtils::get_assimp_string(ai_light->mName)] = l; } //fill camera cache for (size_t c = 0; c < scene->mNumCameras; c++) { aiCamera *ai_camera = scene->mCameras[c]; ERR_CONTINUE(ai_camera == NULL); - state.camera_cache[_assimp_get_string(ai_camera->mName)] = c; + state.camera_cache[AssimpUtils::get_assimp_string(ai_camera->mName)] = c; } if (scene->mRootNode) { - Map bind_xforms; //temporary map to store bind transforms - //guess the skeletons, since assimp does not really support them directly - Map ownership; //bone names to groups - //fill this map with bone names and which group where they detected to, going mesh by mesh - _generate_bone_groups(state, state.assimp_scene->mRootNode, ownership, bind_xforms); - Map skeleton_map; //maps previously created groups to actual skeletons - //generates the skeletons when bones are found in the hierarchy, and follows them (including gaps/holes). - _generate_skeletons(state, state.assimp_scene->mRootNode, ownership, skeleton_map, bind_xforms); //generate nodes for (uint32_t i = 0; i < scene->mRootNode->mNumChildren; i++) { - _generate_node(state, scene->mRootNode->mChildren[i], state.root); + _generate_node(state, NULL, scene->mRootNode->mChildren[i], state.root); } - //assign skeletons to nodes - - for (Map::Element *E = state.mesh_skeletons.front(); E; E = E->next()) { - MeshInstance *mesh = E->key(); - Skeleton *skeleton = E->get(); - NodePath skeleton_path = mesh->get_path_to(skeleton); - mesh->set_skeleton_path(skeleton_path); + // finalize skeleton + for (Map::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + Skeleton *skeleton = key_value_pair->key(); + // convert world to local for skeleton bone rests + skeleton->localize_rests(); } + + print_verbose("generating mesh phase from skeletal mesh"); + generate_mesh_phase_from_skeletal_mesh(state); } if (p_flags & IMPORT_ANIMATION && scene->mNumAnimations) { @@ -601,12 +417,14 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons } } +// 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 = _assimp_anim_string_to_string(anim->mName); + String name = AssimpUtils::get_anim_string_from_assimp(anim->mName); if (name == String()) { name = "Animation " + itos(p_animation_index + 1); } @@ -616,7 +434,7 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim if (state.assimp_scene->mMetaData != NULL && 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 = _get_fbx_fps(time_mode, state.assimp_scene); + ticks_per_second = AssimpUtils::get_fbx_fps(time_mode, state.assimp_scene); } //? @@ -637,36 +455,31 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim for (size_t i = 0; i < anim->mNumChannels; i++) { const aiNodeAnim *track = anim->mChannels[i]; - String node_name = _assimp_get_string(track->mNodeName); - /* - if (node_name.find(ASSIMP_FBX_KEY) != -1) { - String p_track_type = node_name.get_slice(ASSIMP_FBX_KEY, 1); - if (p_track_type == "_Translation" || p_track_type == "_Rotation" || p_track_type == "_Scaling") { - continue; - } - } -*/ + String node_name = AssimpUtils::get_assimp_string(track->mNodeName); + if (track->mNumRotationKeys == 0 && track->mNumPositionKeys == 0 && track->mNumScalingKeys == 0) { continue; //do not bother } - bool is_bone = state.bone_owners.has(node_name); - NodePath node_path; - Skeleton *skeleton = NULL; + for (Map::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + Skeleton *skeleton = key_value_pair->key(); - if (is_bone) { - skeleton = state.skeletons[state.bone_owners[node_name]]; - String path = state.root->get_path_to(skeleton); - path += ":" + node_name; - node_path = path; - } else { + bool is_bone = skeleton->find_bone(node_name) != -1; + //print_verbose("Bone " + node_name + " is bone? " + (is_bone ? "Yes" : "No")); + NodePath node_path; - ERR_CONTINUE(!state.node_map.has(node_name)); - Node *node = state.node_map[node_name]; - node_path = state.root->get_path_to(node); + if (is_bone) { + String path = state.root->get_path_to(skeleton); + path += ":" + node_name; + node_path = path; + } else { + ERR_CONTINUE(!state.node_map.has(node_name)); + Node *node = state.node_map[node_name]; + node_path = state.root->get_path_to(node); + } + + _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name); } - - _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name); } //blend shape tracks @@ -675,7 +488,7 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim const aiMeshMorphAnim *anim_mesh = anim->mMorphMeshChannels[i]; - const String prop_name = _assimp_get_string(anim_mesh->mName); + 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); @@ -715,513 +528,22 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim } } -float EditorSceneImporterAssimp::_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; - p_scene->mMetaData->Get("FrameRate", frame_rate); - return frame_rate; - } - return 0; -} - -Transform EditorSceneImporterAssimp::_get_global_assimp_node_transform(const aiNode *p_current_node) { - aiNode const *current_node = p_current_node; - Transform xform; - while (current_node != NULL) { - xform = _assimp_matrix_transform(current_node->mTransformation) * xform; - current_node = current_node->mParent; - } - return xform; -} - -Ref EditorSceneImporterAssimp::_load_texture(ImportState &state, String p_path) { - Vector 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 >= state.assimp_scene->mNumTextures, Ref()); - aiTexture *tex = state.assimp_scene->mTextures[texture_idx]; - String filename = _assimp_raw_string_to_string(tex->mFilename); - filename = filename.get_file(); - print_verbose("Open Asset Import: Loading embedded texture " + filename); - if (tex->mHeight == 0) { - if (tex->CheckFormat("png")) { - Ref img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref()); - - Ref t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } else if (tex->CheckFormat("jpg")) { - Ref img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref()); - Ref t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } else if (tex->CheckFormat("dds")) { - ERR_FAIL_V_MSG(Ref(), "Open Asset Import: Embedded dds not implemented."); - //Ref img = Image::_dds_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - //ERR_FAIL_COND_V(img.is_null(), Ref()); - //Ref t; - //t.instance(); - //t->create_from_image(img); - //t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - //return t; - } - } else { - Ref 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()); - //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()); - - Ref t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } - return Ref(); - } - Ref p_texture = ResourceLoader::load(p_path, "Texture"); - return p_texture; -} - -Ref EditorSceneImporterAssimp::_generate_material_from_index(ImportState &state, int p_index, bool p_double_sided) { - - ERR_FAIL_INDEX_V(p_index, (int)state.assimp_scene->mNumMaterials, Ref()); - - aiMaterial *ai_material = state.assimp_scene->mMaterials[p_index]; - Ref 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(SpatialMaterial::CULL_DISABLED); - } - } - - //const String mesh_name = _assimp_get_string(ai_mesh->mName); - aiString mat_name; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) { - mat->set_name(_assimp_get_string(mat_name)); - } - - aiTextureType tex_normal = aiTextureType_NORMALS; - { - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; - - if (AI_SUCCESS == ai_material->GetTexture(tex_normal, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _assimp_raw_string_to_string(ai_filename); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - - if (texture.is_valid()) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } - } - } - - { - aiString ai_filename = aiString(); - String filename = ""; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, ai_filename)) { - filename = _assimp_raw_string_to_string(ai_filename); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } - } - } - - aiTextureType tex_emissive = aiTextureType_EMISSIVE; - - if (ai_material->GetTextureCount(tex_emissive) > 0) { - - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; - - if (AI_SUCCESS == ai_material->GetTexture(tex_emissive, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _assimp_raw_string_to_string(ai_filename); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - if (texture != NULL) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true); - mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, texture); - } - } - } - } - - aiTextureType tex_albedo = aiTextureType_DIFFUSE; - if (ai_material->GetTextureCount(tex_albedo) > 0) { - - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; - if (AI_SUCCESS == ai_material->GetTexture(tex_albedo, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _assimp_raw_string_to_string(ai_filename); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() != Image::ALPHA_NONE) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } - } else { - 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_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a)); - } - } - - aiString tex_gltf_base_color_path = aiString(); - aiTextureMapMode map_mode[2]; - if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, &tex_gltf_base_color_path, NULL, NULL, NULL, NULL, map_mode)) { - String filename = _assimp_raw_string_to_string(tex_gltf_base_color_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, pbr_base_color)) { - if (Math::is_equal_approx(pbr_base_color.a, 1.0f) == false) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); - } - } - { - aiString tex_fbx_pbs_base_color_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_base_color_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR, pbr_base_color)) { - mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); - } - } - - aiUVTransform pbr_base_color_uv_xform; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM, pbr_base_color_uv_xform)) { - mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f)); - mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f)); - } - } - - { - aiString tex_fbx_pbs_normal_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE, tex_fbx_pbs_normal_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_normal_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } - } - } - - if (p_double_sided) { - mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); - } - - { - aiString tex_fbx_stingray_normal_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE, tex_fbx_stingray_normal_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_stingray_normal_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } - } - } - - { - aiString tex_fbx_pbs_base_color_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_base_color_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR, pbr_base_color)) { - mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); - } - } - - aiUVTransform pbr_base_color_uv_xform; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM, pbr_base_color_uv_xform)) { - mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f)); - mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f)); - } - } - - { - aiString tex_fbx_pbs_emissive_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE, tex_fbx_pbs_emissive_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_emissive_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - _find_texture_path(state.path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_emmissive_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR, pbr_emmissive_color)) { - mat->set_emission(Color(pbr_emmissive_color.r, pbr_emmissive_color.g, pbr_emmissive_color.b, pbr_emmissive_color.a)); - } - } - - real_t pbr_emission_intensity; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR, pbr_emission_intensity)) { - mat->set_emission_energy(pbr_emission_intensity); - } - } - - aiString tex_gltf_pbr_metallicroughness_path; - if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &tex_gltf_pbr_metallicroughness_path)) { - String filename = _assimp_raw_string_to_string(tex_gltf_pbr_metallicroughness_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); - } - } - } else { - float pbr_roughness = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); - } - float pbr_metallic = 0.0f; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, pbr_metallic)) { - mat->set_metallic(pbr_metallic); - } - } - { - aiString tex_fbx_pbs_metallic_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE, tex_fbx_pbs_metallic_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_metallic_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_metallic = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR, pbr_metallic)) { - mat->set_metallic(pbr_metallic); - } - } - - aiString tex_fbx_pbs_rough_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_rough_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_roughness = 0.04f; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); - } - } - } - - { - aiString tex_fbx_pbs_metallic_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE, tex_fbx_pbs_metallic_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_metallic_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_metallic = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_FACTOR, pbr_metallic)) { - mat->set_metallic(pbr_metallic); - } - } - - aiString tex_fbx_pbs_rough_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) { - String filename = _assimp_raw_string_to_string(tex_fbx_pbs_rough_path); - String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - bool found = false; - _find_texture_path(state.path, path, found); - if (found) { - Ref texture = _load_texture(state, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_roughness = 0.04f; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); - } - } - } - - return mat; -} - -Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &state, const Vector &p_surface_indices, Skeleton *p_skeleton, bool p_double_sided_material) { +// +// Mesh Generation from indicies ? why do we need so much mesh code +// [debt needs looked into] +Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( + ImportState &state, + const Vector &p_surface_indices, + const aiNode *assimp_node, + Skeleton *p_skeleton) { Ref mesh; mesh.instance(); bool has_uvs = false; + // + // 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]; @@ -1231,7 +553,7 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS if (p_skeleton) { for (size_t b = 0; b < ai_mesh->mNumBones; b++) { aiBone *bone = ai_mesh->mBones[b]; - String bone_name = _assimp_get_string(bone->mName); + String bone_name = AssimpUtils::get_assimp_string(bone->mName); int bone_index = p_skeleton->find_bone(bone_name); ERR_CONTINUE(bone_index == -1); //bone refers to an unexisting index, wtf. @@ -1244,7 +566,6 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS 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(); @@ -1255,23 +576,34 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS } } + // + // Create mesh from data from assimp + // + Ref 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->add_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->add_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->add_color(color); } + + // Work out normal calculations? - this needs work it doesn't work properly on huestos if (ai_mesh->mNormals != NULL) { const aiVector3D normals = ai_mesh->mNormals[j]; const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); @@ -1286,6 +618,7 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS } } + // We have vertex weights right? if (vertex_weights.has(j)) { Vector bone_info = vertex_weights[j]; @@ -1293,6 +626,8 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS bones.resize(bone_info.size()); Vector 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; @@ -1302,30 +637,152 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS st->add_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]; - ERR_CONTINUE(face.mNumIndices != 3); - Vector order; - order.push_back(2); - order.push_back(1); - order.push_back(0); - for (int32_t k = 0; k < order.size(); k++) { - st->add_index(face.mIndices[order[k]]); + 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(); } - Ref material; + aiMaterial *ai_material = state.assimp_scene->mMaterials[ai_mesh->mMaterialIndex]; + Ref mat; + mat.instance(); - if (!state.material_cache.has(ai_mesh->mMaterialIndex)) { - material = _generate_material_from_index(state, ai_mesh->mMaterialIndex, p_double_sided_material); + 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(SpatialMaterial::CULL_DISABLED); + } + } + + const String mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mName); + 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(SpatialMaterial::CULL_BACK); + + // Now process materials + 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_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode + } + + mat->set_texture(SpatialMaterial::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_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + mat->set_cull_mode(SpatialMaterial::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 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(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::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(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture); + } + } + } + } + + aiTextureType tex_emissive = aiTextureType_EMISSIVE; + { + String filename = ""; + String path = ""; + Ref 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(SpatialMaterial::FEATURE_EMISSION, true); + mat->set_texture(SpatialMaterial::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(SpatialMaterial::FEATURE_EMISSION, true); + mat->set_texture(SpatialMaterial::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 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(SpatialMaterial::TEXTURE_METALLIC, image_data.texture); + } + } + + aiTextureType tex_roughness = aiTextureType_SHININESS; + { + String filename, path; + Ref 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(SpatialMaterial::TEXTURE_ROUGHNESS, image_data.texture); + } } Array array_mesh = st->commit_to_arrays(); @@ -1335,16 +792,13 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS Map morph_mesh_idx_names; for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) { - if (i == 0) { - //only do this the first time - String ai_anim_mesh_name = _assimp_get_string(ai_mesh->mAnimMeshes[j]->mName); - mesh->set_blend_shape_mode(Mesh::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); + String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName); + mesh->set_blend_shape_mode(Mesh::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); + morph_mesh_idx_names.insert(j, ai_anim_mesh_name); Array array_copy; array_copy.resize(VisualServer::ARRAY_MAX); @@ -1363,12 +817,11 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS vertices.write()[l] = position; } PoolVector3Array new_vertices = array_copy[VisualServer::ARRAY_VERTEX].duplicate(true); - - for (int32_t l = 0; l < vertices.size(); l++) { + ERR_CONTINUE(vertices.size() != new_vertices.size()); + for (int32_t l = 0; l < new_vertices.size(); l++) { PoolVector3Array::Write w = new_vertices.write(); w[l] = vertices[l]; } - ERR_CONTINUE(vertices.size() != new_vertices.size()); array_copy[VisualServer::ARRAY_VERTEX] = new_vertices; } @@ -1382,7 +835,7 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS colors.write()[l] = color; } PoolColorArray new_colors = array_copy[VisualServer::ARRAY_COLOR].duplicate(true); - + ERR_CONTINUE(colors.size() != new_colors.size()); for (int32_t l = 0; l < colors.size(); l++) { PoolColorArray::Write w = new_colors.write(); w[l] = colors[l]; @@ -1394,12 +847,12 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS PoolVector3Array normals; normals.resize(num_vertices); for (size_t l = 0; l < num_vertices; l++) { - const aiVector3D ai_normal = ai_mesh->mAnimMeshes[i]->mNormals[l]; + const aiVector3D ai_normal = ai_mesh->mAnimMeshes[j]->mNormals[l]; Vector3 normal = Vector3(ai_normal.x, ai_normal.y, ai_normal.z); normals.write()[l] = normal; } PoolVector3Array new_normals = array_copy[VisualServer::ARRAY_NORMAL].duplicate(true); - + ERR_CONTINUE(normals.size() != new_normals.size()); for (int l = 0; l < normals.size(); l++) { PoolVector3Array::Write w = new_normals.write(); w[l] = normals[l]; @@ -1412,7 +865,7 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS tangents.resize(num_vertices); PoolColorArray::Write w = tangents.write(); for (size_t l = 0; l < num_vertices; l++) { - _calc_tangent_from_mesh(ai_mesh, j, l, l, w); + AssimpUtils::calc_tangent_from_mesh(ai_mesh, j, l, l, w); } PoolRealArray new_tangents = array_copy[VisualServer::ARRAY_TANGENT].duplicate(true); ERR_CONTINUE(new_tangents.size() != tangents.size() * 4); @@ -1422,340 +875,388 @@ Ref EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS new_tangents.write()[l + 2] = tangents[l].b; new_tangents.write()[l + 3] = tangents[l].a; } - array_copy[VisualServer::ARRAY_TANGENT] = new_tangents; } morphs[j] = array_copy; } - mesh->add_surface_from_arrays(primitive, array_mesh, morphs); - mesh->surface_set_material(i, material); - mesh->surface_set_name(i, _assimp_get_string(ai_mesh->mName)); + mesh->surface_set_material(i, mat); + mesh->surface_set_name(i, AssimpUtils::get_assimp_string(ai_mesh->mName)); } return mesh; } -void EditorSceneImporterAssimp::_generate_node(ImportState &state, const aiNode *p_assimp_node, Node *p_parent) { +/* to be moved 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 NULL; +} + +/** + * Create a new mesh for the node supplied + */ +void EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform) { + /* MESH NODE */ + Ref mesh; + Skeleton *skeleton = NULL; + // see if we have mesh cache for this. + Vector surface_indices; + for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) { + int mesh_index = assimp_node->mMeshes[i]; + aiMesh *ai_mesh = state.assimp_scene->mMeshes[assimp_node->mMeshes[i]]; + + // Map // this is what we need + if (ai_mesh->mNumBones > 0) { + // we only need the first bone to retrieve the skeleton + const aiBone *first = ai_mesh->mBones[0]; + + ERR_FAIL_COND(first == NULL); + + Map::Element *match = state.bone_to_skeleton_lookup.find(first); + if (match != NULL) { + skeleton = match->value(); + + if (skeleton == NULL) { + print_error("failed to find bone skeleton for bone: " + AssimpUtils::get_assimp_string(first->mName)); + } else { + print_verbose("successfully found skeleton for first bone on mesh, can properly handle animations now!"); + } + // I really need the skeleton and bone to be known as this is something flaky in model exporters. + ERR_FAIL_COND(skeleton == NULL); // should not happen if bone was successfully created in previous step. + } + } + 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]); + } + + if (!state.mesh_cache.has(mesh_key)) { + mesh = _generate_mesh_from_surface_indices(state, surface_indices, assimp_node, skeleton); + state.mesh_cache[mesh_key] = mesh; + } + + //Transform transform = recursive_state.node_transform; + + // we must unfortunately overwrite mesh and skeleton transform with armature data + if (skeleton != NULL) { + print_verbose("Applying mesh and skeleton to armature"); + // required for blender, maya etc + Map::Element *match = state.armature_skeletons.find(skeleton); + node_transform = match->value()->get_transform(); + } + + MeshInstance *mesh_node = memnew(MeshInstance); + mesh = state.mesh_cache[mesh_key]; + mesh_node->set_mesh(mesh); + + attach_new_node(state, + mesh_node, + assimp_node, + parent_node, + node_name, + node_transform); + + // set this once and for all + if (skeleton != NULL) { + // root must be informed of its new child + parent_node->add_child(skeleton); + + // owner must be set after adding to tree + skeleton->set_owner(state.root); + + skeleton->set_transform(node_transform); + + // must be done after added to tree + mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton)); + } +} + +/** generate_mesh_phase_from_skeletal_mesh + * This must be executed after generate_nodes because the skeleton doesn't exist until that has completed the first pass + */ +void EditorSceneImporterAssimp::generate_mesh_phase_from_skeletal_mesh(ImportState &state) { + // prevent more than one skeleton existing per mesh + // * multiple root bones have this + // * this simply filters the node out if it has already been added then references the skeleton so we know the actual skeleton for this node + for (Map::Element *key_value_pair = state.assimp_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + const aiNode *assimp_node = key_value_pair->key(); + Node *current_node = (Node *)key_value_pair->value(); + Node *parent_node = current_node->get_parent(); + + ERR_CONTINUE(assimp_node == NULL); + ERR_CONTINUE(parent_node == NULL); + + 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) { + create_mesh(state, assimp_node, node_name, current_node, parent_node, node_transform); + } + } +} + +/** + * attach_new_node + * configures node, assigns parent node +**/ +void EditorSceneImporterAssimp::attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform) { + ERR_FAIL_COND(new_node == NULL); + ERR_FAIL_COND(node == NULL); + ERR_FAIL_COND(parent_node == NULL); + ERR_FAIL_COND(state.root == NULL); + + // assign properties to new godot note + new_node->set_name(Name); + new_node->set_transform(transform); + + // add element as child to parent + parent_node->add_child(new_node); + + // owner must be set after + new_node->set_owner(state.root); + + // cache node mapping results by name and then by aiNode* + state.node_map[Name] = new_node; + state.assimp_node_map[node] = new_node; +} + +/** + * Create a light for the scene + * Automatically caches lights for lookup later + */ +void EditorSceneImporterAssimp::create_light(ImportState &state, RecursiveState &recursive_state) { + Light *light = NULL; + aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[recursive_state.node_name]]; + ERR_FAIL_COND(!ai_light); + + if (ai_light->mType == aiLightSource_DIRECTIONAL) { + light = memnew(DirectionalLight); + Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); + dir.normalize(); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); + Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); + up.normalize(); + + Transform light_transform; + light_transform.set_look_at(pos, pos + dir, up); + + recursive_state.node_transform *= light_transform; + + } else if (ai_light->mType == aiLightSource_POINT) { + light = memnew(OmniLight); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); + Transform xform; + xform.origin = pos; + + recursive_state.node_transform *= xform; + + light->set_transform(xform); + + //light->set_param(Light::PARAM_ATTENUATION, 1); + } else if (ai_light->mType == aiLightSource_SPOT) { + light = memnew(SpotLight); + + Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); + dir.normalize(); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); + Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); + up.normalize(); + + Transform light_transform; + light_transform.set_look_at(pos, pos + dir, up); + recursive_state.node_transform *= light_transform; + + //light->set_param(Light::PARAM_ATTENUATION, 0.0f); + } + ERR_FAIL_COND(light == NULL); + + light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b)); + recursive_state.new_node = light; + + attach_new_node(state, + recursive_state.new_node, + recursive_state.assimp_node, + recursive_state.parent_node, + recursive_state.node_name, + recursive_state.node_transform); +} + +/** + * Create camera for the scene + */ +void EditorSceneImporterAssimp::create_camera(ImportState &state, RecursiveState &recursive_state) { + aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[recursive_state.node_name]]; + ERR_FAIL_COND(!ai_camera); + + Camera *camera = memnew(Camera); + + float near = ai_camera->mClipPlaneNear; + if (Math::is_equal_approx(near, 0.0f)) { + near = 0.1f; + } + camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar); + + Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z); + Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized(); + Vector3 up = Vector3(ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z); + + Transform xform; + xform.set_look_at(pos, look_at, up); + + recursive_state.new_node = camera; + + attach_new_node(state, + recursive_state.new_node, + recursive_state.assimp_node, + recursive_state.parent_node, + recursive_state.node_name, + recursive_state.node_transform); +} + +/** + * Create Bone + * Create a bone in the scene + */ +void EditorSceneImporterAssimp::create_bone(ImportState &state, RecursiveState &recursive_state) { + // for each armature node we must make a new skeleton but ensure it + // has a bone in the child to ensure we don't make too many + // the reason you must do this is because a skeleton exists per mesh? + // and duplicate bone names are very bad for determining what is going on. + aiBone *parent_bone_assimp = get_bone_by_name(state.assimp_scene, recursive_state.assimp_node->mParent->mName); + + // set to true when you want to use skeleton reference from cache. + bool do_not_create_armature = false; + + // prevent more than one skeleton existing per mesh + // * multiple root bones have this + // * this simply filters the node out if it has already been added then references the skeleton so we know the actual skeleton for this node + for (Map::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) { + if (key_value_pair->value() == recursive_state.parent_node) { + // apply the skeleton for this mesh + recursive_state.skeleton = key_value_pair->key(); + + // force this off + do_not_create_armature = true; + } + } + + // check if parent was a bone + // if parent was not a bone this is the first bone. + // therefore parent is the 'armature'? + // also for multi root bone support make sure we don't already have the skeleton cached. + // if we do we must merge them - as this is all godot supports right now. + if (!parent_bone_assimp && recursive_state.skeleton == NULL && !do_not_create_armature) { + // create new skeleton on the root. + recursive_state.skeleton = memnew(Skeleton); + + ERR_FAIL_COND(state.root == NULL); + ERR_FAIL_COND(recursive_state.skeleton == NULL); + + print_verbose("Parent armature node is called " + recursive_state.parent_node->get_name()); + // store root node for this skeleton / used in animation playback and bone detection. + + state.armature_skeletons.insert(recursive_state.skeleton, Object::cast_to(recursive_state.parent_node)); + + //skeleton->set_use_bones_in_world_transform(true); + print_verbose("Created new FBX skeleton for armature node"); + } + + ERR_FAIL_COND_MSG(recursive_state.skeleton == NULL, "Mesh has invalid armature detection - report this"); + + // this transform is a bone + recursive_state.skeleton->add_bone(recursive_state.node_name); + + ERR_FAIL_COND(recursive_state.skeleton == NULL); // serious bug we must now exit. + //ERR_FAIL_COND(recursive_state.skeleton->get_name() == ""); + print_verbose("Bone added to lookup: " + AssimpUtils::get_assimp_string(recursive_state.bone->mName)); + print_verbose("Skeleton attached to: " + recursive_state.skeleton->get_name()); + // make sure to write the bone lookup inverse so we can retrieve the mesh for this bone later + state.bone_to_skeleton_lookup.insert(recursive_state.bone, recursive_state.skeleton); + + Transform xform = AssimpUtils::assimp_matrix_transform(recursive_state.bone->mOffsetMatrix); + recursive_state.skeleton->set_bone_rest(recursive_state.skeleton->get_bone_count() - 1, xform.affine_inverse()); + + // get parent node of assimp node + const aiNode *parent_node_assimp = recursive_state.assimp_node->mParent; + + // ensure we have a parent + if (parent_node_assimp != NULL) { + int parent_bone_id = recursive_state.skeleton->find_bone(AssimpUtils::get_assimp_string(parent_node_assimp->mName)); + int current_bone_id = recursive_state.skeleton->find_bone(recursive_state.node_name); + print_verbose("Parent bone id " + itos(parent_bone_id) + " current bone id" + itos(current_bone_id)); + print_verbose("Bone debug: " + AssimpUtils::get_assimp_string(parent_node_assimp->mName)); + recursive_state.skeleton->set_bone_parent(current_bone_id, parent_bone_id); + } +} + +/** + * Generate node + * Recursive call to iterate over all nodes + */ +void EditorSceneImporterAssimp::_generate_node( + ImportState &state, + Skeleton *skeleton, + const aiNode *assimp_node, Node *parent_node) { + + // sanity check + ERR_FAIL_COND(state.root == NULL); + ERR_FAIL_COND(state.assimp_scene == NULL); + ERR_FAIL_COND(assimp_node == NULL); + ERR_FAIL_COND(parent_node == NULL); Spatial *new_node = NULL; - String node_name = _assimp_get_string(p_assimp_node->mName); - Transform node_transform = _assimp_matrix_transform(p_assimp_node->mTransformation); + String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); + Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); - if (p_assimp_node->mNumMeshes > 0) { - /* MESH NODE */ - Ref mesh; - Skeleton *skeleton = NULL; - { + // can safely return null - is this node a bone? + aiBone *bone = get_bone_by_name(state.assimp_scene, assimp_node->mName); - //see if we have mesh cache for this. - Vector surface_indices; - for (uint32_t i = 0; i < p_assimp_node->mNumMeshes; i++) { - int mesh_index = p_assimp_node->mMeshes[i]; - surface_indices.push_back(mesh_index); + // out arguments helper - for pushing state down into creation functions + RecursiveState recursive_state(node_transform, skeleton, new_node, node_name, assimp_node, parent_node, bone); - //take the chance and attempt to find the skeleton from the bones - if (!skeleton) { - aiMesh *ai_mesh = state.assimp_scene->mMeshes[p_assimp_node->mMeshes[i]]; - for (uint32_t j = 0; j < ai_mesh->mNumBones; j++) { - aiBone *bone = ai_mesh->mBones[j]; - String bone_name = _assimp_get_string(bone->mName); - if (state.bone_owners.has(bone_name)) { - skeleton = state.skeletons[state.bone_owners[bone_name]]; - break; - } - } - } - } - 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]); - } - - if (!state.mesh_cache.has(mesh_key)) { - //adding cache - aiString cull_mode; //cull is on mesh, which is kind of stupid tbh - bool double_sided_material = false; - if (p_assimp_node->mMetaData) { - p_assimp_node->mMetaData->Get("Culling", cull_mode); - } - if (cull_mode.length != 0 && cull_mode == aiString("CullingOff")) { - double_sided_material = true; - } - - mesh = _generate_mesh_from_surface_indices(state, surface_indices, skeleton, double_sided_material); - state.mesh_cache[mesh_key] = mesh; - } - - mesh = state.mesh_cache[mesh_key]; - } - - MeshInstance *mesh_node = memnew(MeshInstance); - if (skeleton) { - state.mesh_skeletons[mesh_node] = skeleton; - } - mesh_node->set_mesh(mesh); - new_node = mesh_node; - - } else if (state.light_cache.has(node_name)) { - - Light *light = NULL; - aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[node_name]]; - ERR_FAIL_COND(!ai_light); - - if (ai_light->mType == aiLightSource_DIRECTIONAL) { - light = memnew(DirectionalLight); - Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); - dir.normalize(); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); - up.normalize(); - - Transform light_transform; - light_transform.set_look_at(pos, pos + dir, up); - - node_transform *= light_transform; - - } else if (ai_light->mType == aiLightSource_POINT) { - light = memnew(OmniLight); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - Transform xform; - xform.origin = pos; - - node_transform *= xform; - - light->set_transform(xform); - - //light->set_param(Light::PARAM_ATTENUATION, 1); - } else if (ai_light->mType == aiLightSource_SPOT) { - light = memnew(SpotLight); - - Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); - dir.normalize(); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); - up.normalize(); - - Transform light_transform; - light_transform.set_look_at(pos, pos + dir, up); - node_transform *= light_transform; - - //light->set_param(Light::PARAM_ATTENUATION, 0.0f); - } - ERR_FAIL_COND(light == NULL); - light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b)); - new_node = light; + // Creation code + if (state.light_cache.has(node_name)) { + create_light(state, recursive_state); } else if (state.camera_cache.has(node_name)) { - - aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[node_name]]; - ERR_FAIL_COND(!ai_camera); - - Camera *camera = memnew(Camera); - - float near = ai_camera->mClipPlaneNear; - if (Math::is_equal_approx(near, 0.0f)) { - near = 0.1f; - } - camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar); - - Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z); - Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized(); - Vector3 up = Vector3(ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z); - - Transform xform; - xform.set_look_at(pos, look_at, up); - - new_node = camera; - } else if (state.bone_owners.has(node_name)) { - - //have to actually put the skeleton somewhere, you know. - Skeleton *skeleton = state.skeletons[state.bone_owners[node_name]]; - if (skeleton->get_parent()) { - //a bone for a skeleton already added.. - //could go downwards here to add meshes children of skeleton bones - //but let's not support it for now. - return; - } - //restore rest poses to local, now that we know where the skeleton finally is - Transform skeleton_transform; - if (p_assimp_node->mParent) { - skeleton_transform = _get_global_assimp_node_transform(p_assimp_node->mParent); - } - for (int i = 0; i < skeleton->get_bone_count(); i++) { - Transform rest = skeleton_transform.affine_inverse() * skeleton->get_bone_rest(i); - skeleton->set_bone_rest(i, rest.affine_inverse()); - } - - skeleton->localize_rests(); - node_name = "Skeleton"; //don't use the bone root name - node_transform = Transform(); //don't transform - - new_node = skeleton; + create_camera(state, recursive_state); + } else if (bone != NULL) { + create_bone(state, recursive_state); } else { //generic node - new_node = memnew(Spatial); + recursive_state.new_node = memnew(Spatial); + attach_new_node(state, + recursive_state.new_node, + recursive_state.assimp_node, + recursive_state.parent_node, + recursive_state.node_name, + recursive_state.node_transform); } - { - - new_node->set_name(node_name); - new_node->set_transform(node_transform); - p_parent->add_child(new_node); - new_node->set_owner(state.root); + // recurse into all child elements + for (size_t i = 0; i < recursive_state.assimp_node->mNumChildren; i++) { + _generate_node(state, recursive_state.skeleton, recursive_state.assimp_node->mChildren[i], + recursive_state.new_node != NULL ? recursive_state.new_node : recursive_state.parent_node); } - - state.node_map[node_name] = new_node; - - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _generate_node(state, p_assimp_node->mChildren[i], new_node); - } -} - -void EditorSceneImporterAssimp::_calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &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; -} - -void EditorSceneImporterAssimp::_set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref texture) { - ERR_FAIL_COND(map_mode == NULL); - aiTextureMapMode tex_mode = aiTextureMapMode::aiTextureMapMode_Wrap; - //for (size_t i = 0; i < 3; i++) { - 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); -} - -void EditorSceneImporterAssimp::_find_texture_path(const String &r_p_path, String &r_path, bool &r_found) { - - _Directory dir; - - List exts; - ImageLoader::get_recognized_extensions(&exts); - - Vector 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; - } - if (r_found == false) { - _find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]); - } - } -} - -void EditorSceneImporterAssimp::_find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension) { - String name = path.get_basename() + extension; - if (dir.file_exists(name)) { - found = true; - path = name; - return; - } - String name_ignore_sub_directory = p_path.get_base_dir().plus_file(path.get_file().get_basename()) + extension; - if (dir.file_exists(name_ignore_sub_directory)) { - found = true; - path = name_ignore_sub_directory; - return; - } - - String name_find_texture_sub_directory = p_path.get_base_dir() + "/textures/" + path.get_file().get_basename() + extension; - if (dir.file_exists(name_find_texture_sub_directory)) { - found = true; - path = name_find_texture_sub_directory; - return; - } - String name_find_texture_upper_sub_directory = p_path.get_base_dir() + "/Textures/" + path.get_file().get_basename() + extension; - if (dir.file_exists(name_find_texture_upper_sub_directory)) { - found = true; - path = name_find_texture_upper_sub_directory; - return; - } - String name_find_texture_outside_sub_directory = p_path.get_base_dir() + "/../textures/" + path.get_file().get_basename() + extension; - if (dir.file_exists(name_find_texture_outside_sub_directory)) { - found = true; - path = name_find_texture_outside_sub_directory; - return; - } - - String name_find_upper_texture_outside_sub_directory = p_path.get_base_dir() + "/../Textures/" + path.get_file().get_basename() + extension; - if (dir.file_exists(name_find_upper_texture_outside_sub_directory)) { - found = true; - path = name_find_upper_texture_outside_sub_directory; - return; - } -} - -String EditorSceneImporterAssimp::_assimp_get_string(const aiString &p_string) const { - //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; - } - - name = name.replace(".", ""); //can break things, specially bone names - - return name; -} - -String EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString &p_string) const { - - 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; -} - -String EditorSceneImporterAssimp::_assimp_raw_string_to_string(const aiString &p_string) const { - String name; - name.parse_utf8(p_string.C_Str() /*,p_string.length*/); - return name; -} - -Ref EditorSceneImporterAssimp::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { - return Ref(); -} - -const Transform EditorSceneImporterAssimp::_assimp_matrix_transform(const aiMatrix4x4 p_matrix) { - aiMatrix4x4 matrix = p_matrix; - Transform xform; - //xform.set(matrix.a1, matrix.b1, matrix.c1, matrix.a2, matrix.b2, matrix.c2, matrix.a3, matrix.b3, matrix.c3, matrix.a4, matrix.b4, matrix.c4); - 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; -} +} \ No newline at end of file diff --git a/modules/assimp/editor_scene_importer_assimp.h b/modules/assimp/editor_scene_importer_assimp.h index 7a30816e3bc..787376c9af7 100644 --- a/modules/assimp/editor_scene_importer_assimp.h +++ b/modules/assimp/editor_scene_importer_assimp.h @@ -44,60 +44,31 @@ #include "scene/resources/animation.h" #include "scene/resources/surface_tool.h" -#include "assimp/DefaultLogger.hpp" -#include "assimp/LogStream.hpp" -#include "assimp/Logger.hpp" -#include "assimp/matrix4x4.h" -#include "assimp/scene.h" -#include "assimp/types.h" +#include +#include +#include +#include +#include +#include + +#include "import_state.h" +#include "import_utils.h" + +using namespace AssimpImporter; class AssimpStream : public Assimp::LogStream { public: // Constructor - AssimpStream(); + AssimpStream() {} // Destructor - ~AssimpStream(); + ~AssimpStream() {} // Write something using your own functionality - void write(const char *message); + void write(const char *message) { + print_verbose(String("Open Asset Import: ") + String(message).strip_edges()); + } }; -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR "$raw.Maya|baseColor", 0, 0 -#define AI_MATKEY_FBX_MAYA_METALNESS_FACTOR "$raw.Maya|metalness", 0, 0 -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR "$raw.Maya|diffuseRoughness", 0, 0 - -#define AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE "$raw.Maya|metalness|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_METALNESS_UV_XFORM "$raw.Maya|metalness|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE "$raw.Maya|diffuseRoughness|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_UV_XFORM "$raw.Maya|diffuseRoughness|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE "$raw.Maya|baseColor|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM "$raw.Maya|baseColor|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE "$raw.Maya|normalCamera|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo", aiTextureType_UNKNOWN, 0 - -#define AI_MATKEY_FBX_NORMAL_TEXTURE "$raw.Maya|normalCamera|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo", aiTextureType_UNKNOWN, 0 - -#define AI_MATKEY_FBX_MAYA_STINGRAY_DISPLACEMENT_SCALING_FACTOR "$raw.Maya|displacementscaling", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR "$raw.Maya|base_color", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR "$raw.Maya|emissive", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR "$raw.Maya|metallic", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR "$raw.Maya|roughness", 0, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR "$raw.Maya|emissive_intensity", 0, 0 - -#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE "$raw.Maya|TEX_normal_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_UV_XFORM "$raw.Maya|TEX_normal_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE "$raw.Maya|TEX_color_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM "$raw.Maya|TEX_color_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE "$raw.Maya|TEX_metallic_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_UV_XFORM "$raw.Maya|TEX_metallic_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE "$raw.Maya|TEX_roughness_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_UV_XFORM "$raw.Maya|TEX_roughness_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE "$raw.Maya|TEX_emissive_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_UV_XFORM "$raw.Maya|TEX_emissive_map|uvtrafo", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_TEXTURE "$raw.Maya|TEX_ao_map|file", aiTextureType_UNKNOWN, 0 -#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_UV_XFORM "$raw.Maya|TEX_ao_map|uvtrafo", aiTextureType_UNKNOWN, 0 - class EditorSceneImporterAssimp : public EditorSceneImporter { private: GDCLASS(EditorSceneImporterAssimp, EditorSceneImporter); @@ -112,59 +83,6 @@ private: }; }; - 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 - }; - }; - - struct ImportState { - - String path; - const aiScene *assimp_scene; - uint32_t max_bone_weights; - Spatial *root; - Map > mesh_cache; - Map > material_cache; - Map light_cache; - Map camera_cache; - Vector skeletons; - Map bone_owners; //maps bones to skeleton index owned by - Map node_map; - Map mesh_skeletons; - bool fbx; //for some reason assimp does some things different for FBX - AnimationPlayer *animation_player; - }; - struct BoneInfo { uint32_t bone; float weight; @@ -177,28 +95,29 @@ private: const aiNode *node; }; - const Transform _assimp_matrix_transform(const aiMatrix4x4 p_matrix); - String _assimp_get_string(const aiString &p_string) const; - Transform _get_global_assimp_node_transform(const aiNode *p_current_node); - void _calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w); void _set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref texture); - void _find_texture_path(const String &p_path, String &path, bool &r_found); - void _find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension); - Ref _load_texture(ImportState &state, String p_path); - Ref _generate_material_from_index(ImportState &state, int p_index, bool p_double_sided); - Ref _generate_mesh_from_surface_indices(ImportState &state, const Vector &p_surface_indices, Skeleton *p_skeleton = NULL, bool p_double_sided_material = false); - void _generate_node(ImportState &state, const aiNode *p_assimp_node, Node *p_parent); - void _generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map &ownership, Map &bind_xforms); - void _fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map &ownership, Map &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector &p_holes, const Map &bind_xforms); - void _generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map &ownership, Map &skeleton_map, const Map &bind_xforms); + Ref _generate_mesh_from_surface_indices(ImportState &state, const Vector &p_surface_indices, const aiNode *assimp_node, Skeleton *p_skeleton = NULL); + // utility for node creation + void attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform); + // simple object creation functions + void create_light(ImportState &state, RecursiveState &recursive_state); + void create_camera(ImportState &state, RecursiveState &recursive_state); + void create_bone(ImportState &state, RecursiveState &recursive_state); + // non recursive - linear so must not use recursive arguments + void create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform); + + // recursive node generator + void _generate_node(ImportState &state, Skeleton *skeleton, const aiNode *assimp_node, Node *parent_node); + // runs after _generate_node as it must then use pre-created godot skeleton. + void generate_mesh_phase_from_skeletal_mesh(ImportState &state); void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int p_track, int p_bake_fps, Ref animation, float ticks_per_second, Skeleton *p_skeleton, const NodePath &p_path, const String &p_name); void _import_animation(ImportState &state, int p_animation_index, int p_bake_fps); - Spatial *_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights); + Spatial *_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights); String _assimp_anim_string_to_string(const aiString &p_string) const; String _assimp_raw_string_to_string(const aiString &p_string) const; @@ -228,7 +147,7 @@ public: virtual void get_extensions(List *r_extensions) const; virtual uint32_t get_import_flags() const; virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List *r_missing_deps, Error *r_err = NULL); - virtual Ref import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps); + Ref load_image(ImportState &state, const aiScene *p_scene, String p_path); }; #endif #endif diff --git a/modules/assimp/import_state.h b/modules/assimp/import_state.h new file mode 100644 index 00000000000..8d82cd3e399 --- /dev/null +++ b/modules/assimp/import_state.h @@ -0,0 +1,115 @@ +/*************************************************************************/ +/* import_state.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_SCENE_IMPORT_STATE_H +#define EDITOR_SCENE_IMPORT_STATE_H + +#include "core/bind/core_bind.h" +#include "core/io/resource_importer.h" +#include "core/vector.h" +#include "editor/import/resource_importer_scene.h" +#include "editor/project_settings_editor.h" +#include "scene/3d/mesh_instance.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/spatial.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/animation.h" +#include "scene/resources/surface_tool.h" + +#include +#include +#include +#include +#include +#include + +namespace AssimpImporter { +/** Import state is for global scene import data + * This makes the code simpler and contains useful lookups. + */ +struct ImportState { + + String path; + const aiScene *assimp_scene; + uint32_t max_bone_weights; + + Spatial *root; + Map > mesh_cache; + Map > material_cache; + Map light_cache; + Map camera_cache; + //Vector skeletons; + Map armature_skeletons; // maps skeletons based on their armature nodes. + Map bone_to_skeleton_lookup; // maps bones back into their skeleton + // very useful for when you need to ask assimp for the bone mesh + Map node_map; + Map assimp_node_map; + Map > path_to_image_cache; + bool fbx; //for some reason assimp does some things different for FBX + AnimationPlayer *animation_player; +}; + +struct AssimpImageData { + Ref raw_image; + Ref texture; + aiTextureMapMode *map_mode = NULL; +}; + +/** 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( + Transform &_node_transform, + Skeleton *_skeleton, + Spatial *_new_node, + const String &_node_name, + const aiNode *_assimp_node, + Node *_parent_node, + const 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; + Skeleton *skeleton; + Spatial *new_node; + const String &node_name; + const aiNode *assimp_node; + Node *parent_node; + const aiBone *bone; +}; +} // namespace AssimpImporter + +#endif // EDITOR_SCENE_IMPORT_STATE_H diff --git a/modules/assimp/import_utils.h b/modules/assimp/import_utils.h new file mode 100644 index 00000000000..4be76ade0fb --- /dev/null +++ b/modules/assimp/import_utils.h @@ -0,0 +1,448 @@ +/*************************************************************************/ +/* import_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef IMPORT_UTILS_IMPORTER_ASSIMP_H +#define IMPORT_UTILS_IMPORTER_ASSIMP_H + +#include "core/io/image_loader.h" +#include "import_state.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, PoolColorArray::Write &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 import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { + return Ref(); + } + + /** + * 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 != NULL) { + 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 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 exts; + ImageLoader::get_recognized_extensions(&exts); + + Vector 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; + } + if (r_found == false) { + 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 texture) { + ERR_FAIL_COND(texture.is_null()); + ERR_FAIL_COND(map_mode == NULL); + aiTextureMapMode tex_mode = aiTextureMapMode::aiTextureMapMode_Wrap; + + 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 :) + */ + static Ref load_image(ImportState &state, const aiScene *p_scene, String p_path) { + + Map >::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 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()); + 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 img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + ERR_FAIL_COND_V(img.is_null(), Ref()); + state.path_to_image_cache.insert(p_path, img); + return img; + } else if (tex->CheckFormat("jpg")) { + Ref img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + ERR_FAIL_COND_V(img.is_null(), Ref()); + state.path_to_image_cache.insert(p_path, img); + return img; + } else if (tex->CheckFormat("dds")) { + ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented"); + ERR_FAIL_COND_V(true, Ref()); + } + } else { + Ref 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()); + //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()); + state.path_to_image_cache.insert(p_path, img); + return img; + } + return Ref(); + } else { + Ref texture = ResourceLoader::load(p_path); + Ref image = texture->get_data(); + state.path_to_image_cache.insert(p_path, image); + return image; + } + + return Ref(); + } + + /* 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; + } +}; + +#endif // IMPORT_UTILS_IMPORTER_ASSIMP_H diff --git a/thirdparty/assimp/assimp/config.h b/thirdparty/assimp/assimp/config.h index 382a698268d..d0e48173495 100644 --- a/thirdparty/assimp/assimp/config.h +++ b/thirdparty/assimp/assimp/config.h @@ -983,6 +983,13 @@ enum aiComponent { #define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f #endif // !! AI_DEBONE_THRESHOLD +#define AI_CONFIG_APP_SCALE_KEY "APP_SCALE_FACTOR" + +#if (!defined AI_CONFIG_APP_SCALE_KEY) +# define AI_CONFIG_APP_SCALE_KEY 1.0 +#endif // AI_CONFIG_APP_SCALE_KEY + + // ---------- All the Build/Compile-time defines ------------ /** @brief Specifies if double precision is supported inside assimp diff --git a/thirdparty/assimp/code/Common/BaseImporter.cpp b/thirdparty/assimp/code/Common/BaseImporter.cpp index 0a5694aa0ea..de5018a2503 100644 --- a/thirdparty/assimp/code/Common/BaseImporter.cpp +++ b/thirdparty/assimp/code/Common/BaseImporter.cpp @@ -76,9 +76,25 @@ BaseImporter::~BaseImporter() { // nothing to do here } +void BaseImporter::UpdateImporterScale( Importer* pImp ) +{ + ai_assert(pImp != nullptr); + ai_assert(importerScale != 0.0); + ai_assert(fileScale != 0.0); + + double activeScale = importerScale * fileScale; + + // Set active scaling + pImp->SetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, activeScale); + + ASSIMP_LOG_DEBUG_F("UpdateImporterScale scale set: %f", activeScale ); +} + // ------------------------------------------------------------------------------------------------ // Imports the given file and returns the imported data. -aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) { +aiScene* BaseImporter::ReadFile(Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) { + + m_progress = pImp->GetProgressHandler(); if (nullptr == m_progress) { return nullptr; @@ -100,6 +116,11 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, { InternReadFile( pFile, sc.get(), &filter); + // Calculate import scale hook - required because pImp not available anywhere else + // passes scale into ScaleProcess + UpdateImporterScale(pImp); + + } catch( const std::exception& err ) { // extract error description m_ErrorText = err.what(); @@ -112,7 +133,7 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, } // ------------------------------------------------------------------------------------------------ -void BaseImporter::SetupProperties(const Importer* /*pImp*/) +void BaseImporter::SetupProperties(const Importer* pImp) { // the default implementation does nothing } @@ -588,6 +609,8 @@ aiScene* BatchLoader::GetImport( unsigned int which ) return nullptr; } + + // ------------------------------------------------------------------------------------------------ void BatchLoader::LoadAll() { diff --git a/thirdparty/assimp/code/FBX/FBXConverter.cpp b/thirdparty/assimp/code/FBX/FBXConverter.cpp index 9f940d3226a..9bd970098ea 100644 --- a/thirdparty/assimp/code/FBX/FBXConverter.cpp +++ b/thirdparty/assimp/code/FBX/FBXConverter.cpp @@ -66,6 +66,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace Assimp { @@ -90,7 +91,6 @@ namespace Assimp { , anim_fps() , out(out) , doc(doc) - , mRemoveEmptyBones( removeEmptyBones ) , mCurrentUnit(FbxUnit::cm) { // animations need to be converted first since this will // populate the node_anim_chain_bits map, which is needed @@ -119,7 +119,6 @@ namespace Assimp { ConvertGlobalSettings(); TransferDataToScene(); - ConvertToUnitScale(unit); // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE // to make sure the scene passes assimp's validation. FBX files @@ -685,30 +684,37 @@ namespace Assimp { bool ok; aiMatrix4x4 chain[TransformationComp_MAXIMUM]; + + ai_assert(TransformationComp_MAXIMUM < 32); + std::uint32_t chainBits = 0; + // A node won't need a node chain if it only has these. + const std::uint32_t chainMaskSimple = (1 << TransformationComp_Translation) + (1 << TransformationComp_Scaling) + (1 << TransformationComp_Rotation); + // A node will need a node chain if it has any of these. + const std::uint32_t chainMaskComplex = ((1 << (TransformationComp_MAXIMUM)) - 1) - chainMaskSimple; + std::fill_n(chain, static_cast(TransformationComp_MAXIMUM), aiMatrix4x4()); // generate transformation matrices for all the different transformation components const float zero_epsilon = 1e-6f; const aiVector3D all_ones(1.0f, 1.0f, 1.0f); - bool is_complex = false; const aiVector3D& PreRotation = PropertyGet(props, "PreRotation", ok); if (ok && PreRotation.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_PreRotation); GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]); } const aiVector3D& PostRotation = PropertyGet(props, "PostRotation", ok); if (ok && PostRotation.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_PostRotation); GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]); } const aiVector3D& RotationPivot = PropertyGet(props, "RotationPivot", ok); if (ok && RotationPivot.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_RotationPivot) | (1 << TransformationComp_RotationPivotInverse); aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]); aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]); @@ -716,21 +722,21 @@ namespace Assimp { const aiVector3D& RotationOffset = PropertyGet(props, "RotationOffset", ok); if (ok && RotationOffset.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_RotationOffset); aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]); } const aiVector3D& ScalingOffset = PropertyGet(props, "ScalingOffset", ok); if (ok && ScalingOffset.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_ScalingOffset); aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]); } const aiVector3D& ScalingPivot = PropertyGet(props, "ScalingPivot", ok); if (ok && ScalingPivot.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_ScalingPivot) | (1 << TransformationComp_ScalingPivotInverse); aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]); aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]); @@ -738,22 +744,28 @@ namespace Assimp { const aiVector3D& Translation = PropertyGet(props, "Lcl Translation", ok); if (ok && Translation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Translation); + aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]); } const aiVector3D& Scaling = PropertyGet(props, "Lcl Scaling", ok); if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Scaling); + aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]); } const aiVector3D& Rotation = PropertyGet(props, "Lcl Rotation", ok); if (ok && Rotation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Rotation); + GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]); } const aiVector3D& GeometricScaling = PropertyGet(props, "GeometricScaling", ok); if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_GeometricScaling); aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]); aiVector3D GeometricScalingInverse = GeometricScaling; bool canscale = true; @@ -768,13 +780,14 @@ namespace Assimp { } } if (canscale) { + chainBits = chainBits | (1 << TransformationComp_GeometricScalingInverse); aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]); } } const aiVector3D& GeometricRotation = PropertyGet(props, "GeometricRotation", ok); if (ok && GeometricRotation.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_GeometricRotation) | (1 << TransformationComp_GeometricRotationInverse); GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]); GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]); chain[TransformationComp_GeometricRotationInverse].Inverse(); @@ -782,7 +795,7 @@ namespace Assimp { const aiVector3D& GeometricTranslation = PropertyGet(props, "GeometricTranslation", ok); if (ok && GeometricTranslation.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_GeometricTranslation) | (1 << TransformationComp_GeometricTranslationInverse); aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]); aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]); } @@ -790,12 +803,12 @@ namespace Assimp { // is_complex needs to be consistent with NeedsComplexTransformationChain() // or the interplay between this code and the animation converter would // not be guaranteed. - ai_assert(NeedsComplexTransformationChain(model) == is_complex); + ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0)); // now, if we have more than just Translation, Scaling and Rotation, // we need to generate a full node chain to accommodate for assimp's // lack to express pivots and offsets. - if (is_complex && doc.Settings().preservePivots) { + if ((chainBits & chainMaskComplex) && doc.Settings().preservePivots) { FBXImporter::LogInfo("generating full transformation chain for node: " + name); // query the anim_chain_bits dictionary to find out which chain elements @@ -808,7 +821,7 @@ namespace Assimp { for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) { const TransformationComp comp = static_cast(i); - if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) { + if ((chainBits & bit) == 0 && (anim_chain_bitmask & bit) == 0) { continue; } @@ -1462,14 +1475,8 @@ namespace Assimp { const WeightIndexArray& indices = cluster->GetIndices(); - if (indices.empty() && mRemoveEmptyBones ) { - continue; - } - const MatIndexArray& mats = geo.GetMaterialIndices(); - bool ok = false; - const size_t no_index_sentinel = std::numeric_limits::max(); count_out_indices.clear(); @@ -1509,8 +1516,7 @@ namespace Assimp { out_indices.push_back(std::distance(outputVertStartIndices->begin(), it)); } - ++count_out_indices.back(); - ok = true; + ++count_out_indices.back(); } } } @@ -1518,10 +1524,8 @@ namespace Assimp { // if we found at least one, generate the output bones // XXX this could be heavily simplified by collecting the bone // data in a single step. - if (ok && mRemoveEmptyBones) { - ConvertCluster(bones, model, *cluster, out_indices, index_out_indices, + ConvertCluster(bones, model, *cluster, out_indices, index_out_indices, count_out_indices, node_global_transform); - } } } catch (std::exception&) { @@ -3532,46 +3536,6 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate()); } - void FBXConverter::ConvertToUnitScale( FbxUnit unit ) { - if (mCurrentUnit == unit) { - return; - } - - ai_real scale = 1.0; - if (mCurrentUnit == FbxUnit::cm) { - if (unit == FbxUnit::m) { - scale = (ai_real)0.01; - } else if (unit == FbxUnit::km) { - scale = (ai_real)0.00001; - } - } else if (mCurrentUnit == FbxUnit::m) { - if (unit == FbxUnit::cm) { - scale = (ai_real)100.0; - } else if (unit == FbxUnit::km) { - scale = (ai_real)0.001; - } - } else if (mCurrentUnit == FbxUnit::km) { - if (unit == FbxUnit::cm) { - scale = (ai_real)100000.0; - } else if (unit == FbxUnit::m) { - scale = (ai_real)1000.0; - } - } - - for (auto mesh : meshes) { - if (nullptr == mesh) { - continue; - } - - if (mesh->HasPositions()) { - for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { - aiVector3D &pos = mesh->mVertices[i]; - pos *= scale; - } - } - } - } - void FBXConverter::TransferDataToScene() { ai_assert(!out->mMeshes); diff --git a/thirdparty/assimp/code/FBX/FBXConverter.h b/thirdparty/assimp/code/FBX/FBXConverter.h index 17a7bc56b7b..b4586273922 100644 --- a/thirdparty/assimp/code/FBX/FBXConverter.h +++ b/thirdparty/assimp/code/FBX/FBXConverter.h @@ -430,10 +430,6 @@ private: void ConvertGlobalSettings(); - // ------------------------------------------------------------------------------------------------ - // Will perform the conversion from a given unit to the requested unit. - void ConvertToUnitScale(FbxUnit unit); - // ------------------------------------------------------------------------------------------------ // copy generated meshes, animations, lights, cameras and textures to the output scene void TransferDataToScene(); @@ -470,9 +466,6 @@ private: aiScene* const out; const FBX::Document& doc; - - bool mRemoveEmptyBones; - FbxUnit mCurrentUnit; }; diff --git a/thirdparty/assimp/code/FBX/FBXDocument.cpp b/thirdparty/assimp/code/FBX/FBXDocument.cpp index 1af08fe6d87..506fd978ddb 100644 --- a/thirdparty/assimp/code/FBX/FBXDocument.cpp +++ b/thirdparty/assimp/code/FBX/FBXDocument.cpp @@ -90,14 +90,6 @@ const Object* LazyObject::Get(bool dieOnError) return object.get(); } - // if this is the root object, we return a dummy since there - // is no root object int he fbx file - it is just referenced - // with id 0. - if(id == 0L) { - object.reset(new Object(id, element, "Model::RootNode")); - return object.get(); - } - const Token& key = element.KeyToken(); const TokenList& tokens = element.Tokens(); diff --git a/thirdparty/assimp/code/FBX/FBXExporter.cpp b/thirdparty/assimp/code/FBX/FBXExporter.cpp index 153e676506d..8ebc8555a22 100644 --- a/thirdparty/assimp/code/FBX/FBXExporter.cpp +++ b/thirdparty/assimp/code/FBX/FBXExporter.cpp @@ -1706,8 +1706,7 @@ void FBXExporter::WriteObjects () } if (end) { break; } } - limbnodes.insert(parent); - skeleton.insert(parent); + // if it was the skeleton root we can finish here if (end) { break; } } @@ -1848,44 +1847,10 @@ void FBXExporter::WriteObjects () inverse_bone_xform.Inverse(); aiMatrix4x4 tr = inverse_bone_xform * mesh_xform; - // this should be the same as the bone's mOffsetMatrix. - // if it's not the same, the skeleton isn't in the bind pose. - float epsilon = 1e-4f; // some error is to be expected - float epsilon_custom = mProperties->GetPropertyFloat("BINDPOSE_EPSILON", -1); - if(epsilon_custom > 0) - epsilon = epsilon_custom; - bool bone_xform_okay = true; - if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) { - not_in_bind_pose.insert(b); - bone_xform_okay = false; - } + sdnode.AddChild("Transform", tr); - // if we have a bone we should use the mOffsetMatrix, - // otherwise try to just use the calculated transform. - if (b) { - sdnode.AddChild("Transform", b->mOffsetMatrix); - } else { - sdnode.AddChild("Transform", tr); - } - // note: it doesn't matter if we mix these, - // because if they disagree we'll throw an exception later. - // it could be that the skeleton is not in the bone pose - // but all bones are still defined, - // in which case this would use the mOffsetMatrix for everything - // and a correct skeleton would still be output. - // transformlink should be the position of the bone in world space. - // if the bone is in the bind pose (or nonexistent), - // we can just use the matrix we already calculated - if (bone_xform_okay) { - sdnode.AddChild("TransformLink", bone_xform); - // otherwise we can only work it out using the mesh position. - } else { - aiMatrix4x4 trl = b->mOffsetMatrix; - trl.Inverse(); - trl *= mesh_xform; - sdnode.AddChild("TransformLink", trl); - } + sdnode.AddChild("TransformLink", bone_xform); // note: this means we ALWAYS rely on the mesh node transform // being unchanged from the time the skeleton was bound. // there's not really any way around this at the moment. diff --git a/thirdparty/assimp/code/FBX/FBXImporter.cpp b/thirdparty/assimp/code/FBX/FBXImporter.cpp index ec8bbd2b47c..bd359dbf295 100644 --- a/thirdparty/assimp/code/FBX/FBXImporter.cpp +++ b/thirdparty/assimp/code/FBX/FBXImporter.cpp @@ -189,8 +189,16 @@ void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS if (settings.convertToMeters) { unit = FbxUnit::m; } + // convert the FBX DOM to aiScene - ConvertToAssimpScene(pScene,doc, settings.removeEmptyBones, unit); + ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones, unit); + + // size relative to cm + float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor(); + + // Set FBX file scale is relative to CM must be converted to M for + // assimp universal format (M) + SetFileScale( size_relative_to_cm * 0.01f); std::for_each(tokens.begin(),tokens.end(),Util::delete_fun()); } diff --git a/thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp b/thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp index 44a0264ca06..5c9a0e309d5 100644 --- a/thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp +++ b/thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp @@ -115,7 +115,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin if(tempVerts.empty()) { FBXImporter::LogWarn("encountered mesh with no vertices"); - return; } std::vector tempFaces; @@ -123,7 +122,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin if(tempFaces.empty()) { FBXImporter::LogWarn("encountered mesh with no faces"); - return; } m_vertices.reserve(tempFaces.size()); @@ -612,7 +610,10 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector& materials_out, cons const std::string& ReferenceInformationType) { const size_t face_count = m_faces.size(); - ai_assert(face_count); + if(face_count <= 0) + { + return; + } // materials are handled separately. First of all, they are assigned per-face // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect diff --git a/thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp b/thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp index b30f39c274c..a3f7dd2557c 100644 --- a/thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp +++ b/thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp @@ -212,7 +212,7 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) // project tangent and bitangent into the plane formed by the vertex' normal aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]); aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]); - localTangent.Normalize(); localBitangent.Normalize(); + localTangent.NormalizeSafe(); localBitangent.NormalizeSafe(); // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN. bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z); @@ -220,10 +220,10 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) if (invalid_tangent != invalid_bitangent) { if (invalid_tangent) { localTangent = meshNorm[p] ^ localBitangent; - localTangent.Normalize(); + localTangent.NormalizeSafe(); } else { localBitangent = localTangent ^ meshNorm[p]; - localBitangent.Normalize(); + localBitangent.NormalizeSafe(); } } diff --git a/thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp b/thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp index 6d458c4b11f..ac770c41f2b 100644 --- a/thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp +++ b/thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp @@ -39,19 +39,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#ifndef ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS - #include "ScaleProcess.h" #include #include +#include namespace Assimp { ScaleProcess::ScaleProcess() : BaseProcess() , mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) { - // empty } ScaleProcess::~ScaleProcess() { @@ -71,10 +69,26 @@ bool ScaleProcess::IsActive( unsigned int pFlags ) const { } void ScaleProcess::SetupProperties( const Importer* pImp ) { - mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 0 ); + // User scaling + mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 1.0f ); + + // File scaling * Application Scaling + float importerScale = pImp->GetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, 1.0f ); + + // apply scale to the scale + // helps prevent bugs with backward compatibility for anyone using normal scaling. + mScale *= importerScale; } void ScaleProcess::Execute( aiScene* pScene ) { + if(mScale == 1.0f) { + return; // nothing to scale + } + + ai_assert( mScale != 0 ); + ai_assert( nullptr != pScene ); + ai_assert( nullptr != pScene->mRootNode ); + if ( nullptr == pScene ) { return; } @@ -82,22 +96,113 @@ void ScaleProcess::Execute( aiScene* pScene ) { if ( nullptr == pScene->mRootNode ) { return; } + + // Process animations and update position transform to new unit system + for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ ) + { + aiAnimation* animation = pScene->mAnimations[animationID]; + + for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++) + { + aiNodeAnim* anim = animation->mChannels[animationChannel]; + + for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++) + { + aiVectorKey& vectorKey = anim->mPositionKeys[posKey]; + vectorKey.mValue *= mScale; + } + } + } + + for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++) + { + aiMesh *mesh = pScene->mMeshes[meshID]; + + // Reconstruct mesh vertexes to the new unit system + for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++) + { + aiVector3D& vertex = mesh->mVertices[vertexID]; + vertex *= mScale; + } + + + // bone placement / scaling + for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++) + { + // Reconstruct matrix by transform rather than by scale + // This prevent scale values being changed which can + // be meaningful in some cases + // like when you want the modeller to see 1:1 compatibility. + aiBone* bone = mesh->mBones[boneID]; + + aiVector3D pos, scale; + aiQuaternion rotation; + + bone->mOffsetMatrix.Decompose( scale, rotation, pos); + + aiMatrix4x4 translation; + aiMatrix4x4::Translation( pos * mScale, translation ); + + aiMatrix4x4 scaling; + aiMatrix4x4::Scaling( aiVector3D(scale), scaling ); + + aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix()); + + bone->mOffsetMatrix = translation * RotMatrix * scaling; + } + + + // animation mesh processing + // convert by position rather than scale. + for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++) + { + aiAnimMesh * animMesh = mesh->mAnimMeshes[animMeshID]; + + for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++) + { + aiVector3D& vertex = animMesh->mVertices[vertexID]; + vertex *= mScale; + } + } + } traverseNodes( pScene->mRootNode ); } -void ScaleProcess::traverseNodes( aiNode *node ) { +void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) { applyScaling( node ); + + for( size_t i = 0; i < node->mNumChildren; i++) + { + // recurse into the tree until we are done! + traverseNodes( node->mChildren[i], nested_node_id+1 ); + } } void ScaleProcess::applyScaling( aiNode *currentNode ) { if ( nullptr != currentNode ) { - currentNode->mTransformation.a1 = currentNode->mTransformation.a1 * mScale; - currentNode->mTransformation.b2 = currentNode->mTransformation.b2 * mScale; - currentNode->mTransformation.c3 = currentNode->mTransformation.c3 * mScale; + // Reconstruct matrix by transform rather than by scale + // This prevent scale values being changed which can + // be meaningful in some cases + // like when you want the modeller to + // see 1:1 compatibility. + + aiVector3D pos, scale; + aiQuaternion rotation; + currentNode->mTransformation.Decompose( scale, rotation, pos); + + aiMatrix4x4 translation; + aiMatrix4x4::Translation( pos * mScale, translation ); + + aiMatrix4x4 scaling; + + // note: we do not use mScale here, this is on purpose. + aiMatrix4x4::Scaling( scale, scaling ); + + aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix()); + + currentNode->mTransformation = translation * RotMatrix * scaling; } } } // Namespace Assimp - -#endif // !! ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS diff --git a/thirdparty/assimp/code/PostProcessing/ScaleProcess.h b/thirdparty/assimp/code/PostProcessing/ScaleProcess.h index 25673787593..468a216736b 100644 --- a/thirdparty/assimp/code/PostProcessing/ScaleProcess.h +++ b/thirdparty/assimp/code/PostProcessing/ScaleProcess.h @@ -39,7 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#pragma once +#ifndef SCALE_PROCESS_H_ +#define SCALE_PROCESS_H_ #include "Common/BaseProcess.h" @@ -53,6 +54,11 @@ namespace Assimp { // --------------------------------------------------------------------------- /** ScaleProcess: Class to rescale the whole model. + * Now rescales animations, bones, and blend shapes properly. + * Please note this will not write to 'scale' transform it will rewrite mesh + * and matrixes so that your scale values + * from your model package are preserved, so this is completely intentional + * bugs should be reported as soon as they are found. */ class ASSIMP_API ScaleProcess : public BaseProcess { public: @@ -78,7 +84,7 @@ public: virtual void Execute( aiScene* pScene ); private: - void traverseNodes( aiNode *currentNode ); + void traverseNodes( aiNode *currentNode, unsigned int nested_node_id = 0 ); void applyScaling( aiNode *currentNode ); private: @@ -86,3 +92,6 @@ private: }; } // Namespace Assimp + + +#endif // SCALE_PROCESS_H_ \ No newline at end of file diff --git a/thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes b/thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes new file mode 100644 index 00000000000..364411a23d6 --- /dev/null +++ b/thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes @@ -0,0 +1,12 @@ +utf8 cpp library +Release 2.3.4 + +A minor bug fix release. Thanks to all who reported bugs. + +Note: Version 2.3.3 contained a regression, and therefore was removed. + +Changes from version 2.3.2 +- Bug fix [39]: checked.h Line 273 and unchecked.h Line 182 have an extra ';' +- Bug fix [36]: replace_invalid() only works with back_inserter + +Files included in the release: utf8.h, core.h, checked.h, unchecked.h, utf8cpp.html, ReleaseNotes diff --git a/thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html b/thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html new file mode 100644 index 00000000000..6f2aacbe7bc --- /dev/null +++ b/thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html @@ -0,0 +1,1789 @@ + + + + + + + + + UTF8-CPP: UTF-8 with C++ in a Portable Way + + + + +

+ UTF8-CPP: UTF-8 with C++ in a Portable Way +

+

+ The Sourceforge project page +

+ +

+ Introduction +

+

+ Many C++ developers miss an easy and portable way of handling Unicode encoded + strings. The original C++ Standard (known as C++98 or C++03) is Unicode agnostic. + C++11 provides some support for Unicode on core language and library level: + u8, u, and U character and string literals, char16_t and char32_t character types, + u16string and u32string library classes, and codecvt support for conversions + between Unicode encoding forms. + In the meantime, developers use third party libraries like ICU, OS specific capabilities, or simply + roll out their own solutions. +

+

+ In order to easily handle UTF-8 encoded Unicode strings, I came up with a small + generic library. For anybody used to work with STL algorithms and iterators, it should be + easy and natural to use. The code is freely available for any purpose - check out + the license at the beginning of the utf8.h file. If you run into + bugs or performance issues, please let me know and I'll do my best to address them. +

+

+ The purpose of this article is not to offer an introduction to Unicode in general, + and UTF-8 in particular. If you are not familiar with Unicode, be sure to check out + Unicode Home Page or some other source of + information for Unicode. Also, it is not my aim to advocate the use of UTF-8 + encoded strings in C++ programs; if you want to handle UTF-8 encoded strings from + C++, I am sure you have good reasons for it. +

+

+ Examples of use +

+

+ Introductionary Sample +

+

+ To illustrate the use of the library, let's start with a small but complete program + that opens a file containing UTF-8 encoded text, reads it line by line, checks each line + for invalid UTF-8 byte sequences, and converts it to UTF-16 encoding and back to UTF-8: +

+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+#include "utf8.h"
+using namespace std;
+int main(int argc, char** argv)
+{
+    if (argc != 2) {
+        cout << "\nUsage: docsample filename\n";
+        return 0;
+    }
+
+    const char* test_file_path = argv[1];
+    // Open the test file (contains UTF-8 encoded text)
+    ifstream fs8(test_file_path);
+    if (!fs8.is_open()) {
+    cout << "Could not open " << test_file_path << endl;
+    return 0;
+    }
+
+    unsigned line_count = 1;
+    string line;
+    // Play with all the lines in the file
+    while (getline(fs8, line)) {
+       // check for invalid utf-8 (for a simple yes/no check, there is also utf8::is_valid function)
+        string::iterator end_it = utf8::find_invalid(line.begin(), line.end());
+        if (end_it != line.end()) {
+            cout << "Invalid UTF-8 encoding detected at line " << line_count << "\n";
+            cout << "This part is fine: " << string(line.begin(), end_it) << "\n";
+        }
+
+        // Get the line length (at least for the valid part)
+        int length = utf8::distance(line.begin(), end_it);
+        cout << "Length of line " << line_count << " is " << length <<  "\n";
+
+        // Convert it to utf-16
+        vector<unsigned short> utf16line;
+        utf8::utf8to16(line.begin(), end_it, back_inserter(utf16line));
+
+        // And back to utf-8
+        string utf8line; 
+        utf8::utf16to8(utf16line.begin(), utf16line.end(), back_inserter(utf8line));
+
+        // Confirm that the conversion went OK:
+        if (utf8line != string(line.begin(), end_it))
+            cout << "Error in UTF-16 conversion at line: " << line_count << "\n";        
+
+        line_count++;
+    }
+    return 0;
+}
+
+

+ In the previous code sample, for each line we performed + a detection of invalid UTF-8 sequences with find_invalid; the number + of characters (more precisely - the number of Unicode code points, including the end + of line and even BOM if there is one) in each line was + determined with a use of utf8::distance; finally, we have converted + each line to UTF-16 encoding with utf8to16 and back to UTF-8 with + utf16to8. +

+

Checking if a file contains valid UTF-8 text

+

+Here is a function that checks whether the content of a file is valid UTF-8 encoded text without +reading the content into the memory: +

+
    
+bool valid_utf8_file(iconst char* file_name)
+{
+    ifstream ifs(file_name);
+    if (!ifs)
+        return false; // even better, throw here
+
+    istreambuf_iterator<char> it(ifs.rdbuf());
+    istreambuf_iterator<char> eos;
+
+    return utf8::is_valid(it, eos);
+}
+
+

+Because the function utf8::is_valid() works with input iterators, we were able +to pass an istreambuf_iterator to it and read the content of the file directly +without loading it to the memory first.

+

+Note that other functions that take input iterator arguments can be used in a similar way. For +instance, to read the content of a UTF-8 encoded text file and convert the text to UTF-16, just +do something like: +

+
+    utf8::utf8to16(it, eos, back_inserter(u16string));
+
+

Ensure that a string contains valid UTF-8 text

+

+If we have some text that "probably" contains UTF-8 encoded text and we want to +replace any invalid UTF-8 sequence with a replacement character, something like +the following function may be used: +

+
+void fix_utf8_string(std::string& str)
+{
+    std::string temp;
+    utf8::replace_invalid(str.begin(), str.end(), back_inserter(temp));
+    str = temp;
+}
+
+

The function will replace any invalid UTF-8 sequence with a Unicode replacement character. +There is an overloaded function that enables the caller to supply their own replacement character. +

+

+ Reference +

+

+ Functions From utf8 Namespace +

+

+ utf8::append +

+

+ Available in version 1.0 and later. +

+

+ Encodes a 32 bit code point as a UTF-8 sequence of octets and appends the sequence + to a UTF-8 string. +

+
+template <typename octet_iterator>
+octet_iterator append(uint32_t cp, octet_iterator result);
+   
+
+

+ octet_iterator: an output iterator.
+ cp: a 32 bit integer representing a code point to append to the + sequence.
+ result: an output iterator to the place in the sequence where to + append the code point.
+ Return value: an iterator pointing to the place + after the newly appended sequence. +

+

+ Example of use: +

+
+unsigned char u[5] = {0,0,0,0,0};
+unsigned char* end = append(0x0448, u);
+assert (u[0] == 0xd1 && u[1] == 0x88 && u[2] == 0 && u[3] == 0 && u[4] == 0);
+
+

+ Note that append does not allocate any memory - it is the burden of + the caller to make sure there is enough memory allocated for the operation. To make + things more interesting, append can add anywhere between 1 and 4 + octets to the sequence. In practice, you would most often want to use + std::back_inserter to ensure that the necessary memory is allocated. +

+

+ In case of an invalid code point, a utf8::invalid_code_point exception + is thrown. +

+

+ utf8::next +

+

+ Available in version 1.0 and later. +

+

+ Given the iterator to the beginning of the UTF-8 sequence, it returns the code + point and moves the iterator to the next position. +

+
+template <typename octet_iterator> 
+uint32_t next(octet_iterator& it, octet_iterator end);
+   
+
+

+ octet_iterator: an input iterator.
+ it: a reference to an iterator pointing to the beginning of an UTF-8 + encoded code point. After the function returns, it is incremented to point to the + beginning of the next code point.
+ end: end of the UTF-8 sequence to be processed. If it + gets equal to end during the extraction of a code point, an + utf8::not_enough_room exception is thrown.
+ Return value: the 32 bit representation of the + processed UTF-8 code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+int cp = next(w, twochars + 6);
+assert (cp == 0x65e5);
+assert (w == twochars + 3);
+
+

+ This function is typically used to iterate through a UTF-8 encoded string. +

+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. +

+

+ utf8::peek_next +

+

+ Available in version 2.1 and later. +

+

+ Given the iterator to the beginning of the UTF-8 sequence, it returns the code + point for the following sequence without changing the value of the iterator. +

+
+template <typename octet_iterator> 
+uint32_t peek_next(octet_iterator it, octet_iterator end);
+   
+
+

+ octet_iterator: an input iterator.
+ it: an iterator pointing to the beginning of an UTF-8 + encoded code point.
+ end: end of the UTF-8 sequence to be processed. If it + gets equal to end during the extraction of a code point, an + utf8::not_enough_room exception is thrown.
+ Return value: the 32 bit representation of the + processed UTF-8 code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+int cp = peek_next(w, twochars + 6);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. +

+

+ utf8::prior +

+

+ Available in version 1.02 and later. +

+

+ Given a reference to an iterator pointing to an octet in a UTF-8 sequence, it + decreases the iterator until it hits the beginning of the previous UTF-8 encoded + code point and returns the 32 bits representation of the code point. +

+
+template <typename octet_iterator> 
+uint32_t prior(octet_iterator& it, octet_iterator start);
+   
+
+

+ octet_iterator: a bidirectional iterator.
+ it: a reference pointing to an octet within a UTF-8 encoded string. + After the function returns, it is decremented to point to the beginning of the + previous code point.
+ start: an iterator to the beginning of the sequence where the search + for the beginning of a code point is performed. It is a + safety measure to prevent passing the beginning of the string in the search for a + UTF-8 lead octet.
+ Return value: the 32 bit representation of the + previous code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+unsigned char* w = twochars + 3;
+int cp = prior (w, twochars);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ This function has two purposes: one is two iterate backwards through a UTF-8 + encoded string. Note that it is usually a better idea to iterate forward instead, + since utf8::next is faster. The second purpose is to find a beginning + of a UTF-8 sequence if we have a random position within a string. Note that in that + case utf8::prior may not detect an invalid UTF-8 sequence in some scenarios: + for instance if there are superfluous trail octets, it will just skip them. +

+

+ it will typically point to the beginning of + a code point, and start will point to the + beginning of the string to ensure we don't go backwards too far. it is + decreased until it points to a lead UTF-8 octet, and then the UTF-8 sequence + beginning with that octet is decoded to a 32 bit representation and returned. +

+

+ In case start is reached before a UTF-8 lead octet is hit, or if an + invalid UTF-8 sequence is started by the lead octet, an invalid_utf8 + exception is thrown. +

+

In case start equals it, a not_enough_room + exception is thrown. +

+ utf8::previous +

+

+ Deprecated in version 1.02 and later. +

+

+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it + decreases the iterator until it hits the beginning of the previous UTF-8 encoded + code point and returns the 32 bits representation of the code point. +

+
+template <typename octet_iterator> 
+uint32_t previous(octet_iterator& it, octet_iterator pass_start);
+   
+
+

+ octet_iterator: a random access iterator.
+ it: a reference pointing to an octet within a UTF-8 encoded string. + After the function returns, it is decremented to point to the beginning of the + previous code point.
+ pass_start: an iterator to the point in the sequence where the search + for the beginning of a code point is aborted if no result was reached. It is a + safety measure to prevent passing the beginning of the string in the search for a + UTF-8 lead octet.
+ Return value: the 32 bit representation of the + previous code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+unsigned char* w = twochars + 3;
+int cp = previous (w, twochars - 1);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ utf8::previous is deprecated, and utf8::prior should + be used instead, although the existing code can continue using this function. + The problem is the parameter pass_start that points to the position + just before the beginning of the sequence. Standard containers don't have the + concept of "pass start" and the function can not be used with their iterators. +

+

+ it will typically point to the beginning of + a code point, and pass_start will point to the octet just before the + beginning of the string to ensure we don't go backwards too far. it is + decreased until it points to a lead UTF-8 octet, and then the UTF-8 sequence + beginning with that octet is decoded to a 32 bit representation and returned. +

+

+ In case pass_start is reached before a UTF-8 lead octet is hit, or if an + invalid UTF-8 sequence is started by the lead octet, an invalid_utf8 + exception is thrown +

+

+ utf8::advance +

+

+ Available in version 1.0 and later. +

+

+ Advances an iterator by the specified number of code points within an UTF-8 + sequence. +

+
+template <typename octet_iterator, typename distance_type> 
+void advance (octet_iterator& it, distance_type n, octet_iterator end);
+   
+
+

+ octet_iterator: an input iterator.
+ distance_type: an integral type convertible to octet_iterator's difference type.
+ it: a reference to an iterator pointing to the beginning of an UTF-8 + encoded code point. After the function returns, it is incremented to point to the + nth following code point.
+ n: a positive integer that shows how many code points we want to + advance.
+ end: end of the UTF-8 sequence to be processed. If it + gets equal to end during the extraction of a code point, an + utf8::not_enough_room exception is thrown.
+

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+unsigned char* w = twochars;
+advance (w, 2, twochars + 6);
+assert (w == twochars + 5);
+
+

+ This function works only "forward". In case of a negative n, there is + no effect. +

+

+ In case of an invalid code point, a utf8::invalid_code_point exception + is thrown. +

+

+ utf8::distance +

+

+ Available in version 1.0 and later. +

+

+ Given the iterators to two UTF-8 encoded code points in a seqence, returns the + number of code points between them. +

+
+template <typename octet_iterator> 
+typename std::iterator_traits<octet_iterator>::difference_type distance (octet_iterator first, octet_iterator last);
+   
+
+

+ octet_iterator: an input iterator.
+ first: an iterator to a beginning of a UTF-8 encoded code point.
+ last: an iterator to a "post-end" of the last UTF-8 encoded code + point in the sequence we are trying to determine the length. It can be the + beginning of a new code point, or not.
+ Return value the distance between the iterators, + in code points. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+size_t dist = utf8::distance(twochars, twochars + 5);
+assert (dist == 2);
+
+

+ This function is used to find the length (in code points) of a UTF-8 encoded + string. The reason it is called distance, rather than, say, + length is mainly because developers are used that length is an + O(1) function. Computing the length of an UTF-8 string is a linear operation, and + it looked better to model it after std::distance algorithm. +

+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. If last does not point to the past-of-end of a UTF-8 seqence, + a utf8::not_enough_room exception is thrown. +

+

+ utf8::utf16to8 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-16 encoded string to UTF-8. +

+
+template <typename u16bit_iterator, typename octet_iterator>
+octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result);
+   
+
+

+ u16bit_iterator: an input iterator.
+ octet_iterator: an output iterator.
+ start: an iterator pointing to the beginning of the UTF-16 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-16 encoded + string to convert.
+ result: an output iterator to the place in the UTF-8 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-8 string. +

+

+ Example of use: +

+
+unsigned short utf16string[] = {0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e};
+vector<unsigned char> utf8result;
+utf16to8(utf16string, utf16string + 5, back_inserter(utf8result));
+assert (utf8result.size() == 10);    
+
+

+ In case of invalid UTF-16 sequence, a utf8::invalid_utf16 exception is + thrown. +

+

+ utf8::utf8to16 +

+

+ Available in version 1.0 and later. +

+

+ Converts an UTF-8 encoded string to UTF-16 +

+
+template <typename u16bit_iterator, typename octet_iterator>
+u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result);
+   
+
+

+ octet_iterator: an input iterator.
+ u16bit_iterator: an output iterator.
+ start: an iterator pointing to the beginning of the UTF-8 encoded + string to convert. < br /> end: an iterator pointing to + pass-the-end of the UTF-8 encoded string to convert.
+ result: an output iterator to the place in the UTF-16 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-16 string. +

+

+ Example of use: +

+
+char utf8_with_surrogates[] = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e";
+vector <unsigned short> utf16result;
+utf8to16(utf8_with_surrogates, utf8_with_surrogates + 9, back_inserter(utf16result));
+assert (utf16result.size() == 4);
+assert (utf16result[2] == 0xd834);
+assert (utf16result[3] == 0xdd1e);
+
+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. If end does not point to the past-of-end of a UTF-8 seqence, a + utf8::not_enough_room exception is thrown. +

+

+ utf8::utf32to8 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-32 encoded string to UTF-8. +

+
+template <typename octet_iterator, typename u32bit_iterator>
+octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result);
+   
+
+

+ octet_iterator: an output iterator.
+ u32bit_iterator: an input iterator.
+ start: an iterator pointing to the beginning of the UTF-32 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-32 encoded + string to convert.
+ result: an output iterator to the place in the UTF-8 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-8 string. +

+

+ Example of use: +

+
+int utf32string[] = {0x448, 0x65E5, 0x10346, 0};
+vector<unsigned char> utf8result;
+utf32to8(utf32string, utf32string + 3, back_inserter(utf8result));
+assert (utf8result.size() == 9);
+
+

+ In case of invalid UTF-32 string, a utf8::invalid_code_point exception + is thrown. +

+

+ utf8::utf8to32 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-8 encoded string to UTF-32. +

+
+template <typename octet_iterator, typename u32bit_iterator>
+u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result);
+   
+
+

+ octet_iterator: an input iterator.
+ u32bit_iterator: an output iterator.
+ start: an iterator pointing to the beginning of the UTF-8 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-8 encoded string + to convert.
+ result: an output iterator to the place in the UTF-32 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-32 string. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+vector<int> utf32result;
+utf8to32(twochars, twochars + 5, back_inserter(utf32result));
+assert (utf32result.size() == 2);
+
+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. If end does not point to the past-of-end of a UTF-8 seqence, a + utf8::not_enough_room exception is thrown. +

+

+ utf8::find_invalid +

+

+ Available in version 1.0 and later. +

+

+ Detects an invalid sequence within a UTF-8 string. +

+
+template <typename octet_iterator> 
+octet_iterator find_invalid(octet_iterator start, octet_iterator end);
+
+

+ octet_iterator: an input iterator.
+ start: an iterator pointing to the beginning of the UTF-8 string to + test for validity.
+ end: an iterator pointing to pass-the-end of the UTF-8 string to test + for validity.
+ Return value: an iterator pointing to the first + invalid octet in the UTF-8 string. In case none were found, equals + end. +

+

+ Example of use: +

+
+char utf_invalid[] = "\xe6\x97\xa5\xd1\x88\xfa";
+char* invalid = find_invalid(utf_invalid, utf_invalid + 6);
+assert (invalid == utf_invalid + 5);
+
+

+ This function is typically used to make sure a UTF-8 string is valid before + processing it with other functions. It is especially important to call it if before + doing any of the unchecked operations on it. +

+

+ utf8::is_valid +

+

+ Available in version 1.0 and later. +

+

+ Checks whether a sequence of octets is a valid UTF-8 string. +

+
+template <typename octet_iterator> 
+bool is_valid(octet_iterator start, octet_iterator end);
+   
+
+

+ octet_iterator: an input iterator.
+ start: an iterator pointing to the beginning of the UTF-8 string to + test for validity.
+ end: an iterator pointing to pass-the-end of the UTF-8 string to test + for validity.
+ Return value: true if the sequence + is a valid UTF-8 string; false if not. +

+ Example of use: +
+char utf_invalid[] = "\xe6\x97\xa5\xd1\x88\xfa";
+bool bvalid = is_valid(utf_invalid, utf_invalid + 6);
+assert (bvalid == false);
+
+

+ is_valid is a shorthand for find_invalid(start, end) == + end;. You may want to use it to make sure that a byte seqence is a valid + UTF-8 string without the need to know where it fails if it is not valid. +

+

+ utf8::replace_invalid +

+

+ Available in version 2.0 and later. +

+

+ Replaces all invalid UTF-8 sequences within a string with a replacement marker. +

+
+template <typename octet_iterator, typename output_iterator>
+output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement);
+template <typename octet_iterator, typename output_iterator>
+output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out);
+   
+
+

+ octet_iterator: an input iterator.
+ output_iterator: an output iterator.
+ start: an iterator pointing to the beginning of the UTF-8 string to + look for invalid UTF-8 sequences.
+ end: an iterator pointing to pass-the-end of the UTF-8 string to look + for invalid UTF-8 sequences.
+ out: An output iterator to the range where the result of replacement + is stored.
+ replacement: A Unicode code point for the replacement marker. The + version without this parameter assumes the value 0xfffd
+ Return value: An iterator pointing to the place + after the UTF-8 string with replaced invalid sequences. +

+

+ Example of use: +

+
+char invalid_sequence[] = "a\x80\xe0\xa0\xc0\xaf\xed\xa0\x80z";
+vector<char> replace_invalid_result;
+replace_invalid (invalid_sequence, invalid_sequence + sizeof(invalid_sequence), back_inserter(replace_invalid_result), '?');
+bvalid = is_valid(replace_invalid_result.begin(), replace_invalid_result.end());
+assert (bvalid);
+char* fixed_invalid_sequence = "a????z";
+assert (std::equal(replace_invalid_result.begin(), replace_invalid_result.end(), fixed_invalid_sequence));
+
+

+ replace_invalid does not perform in-place replacement of invalid + sequences. Rather, it produces a copy of the original string with the invalid + sequences replaced with a replacement marker. Therefore, out must not + be in the [start, end] range. +

+

+ If end does not point to the past-of-end of a UTF-8 sequence, a + utf8::not_enough_room exception is thrown. +

+

+ utf8::starts_with_bom +

+

+ Available in version 2.3 and later. Relaces deprecated is_bom() function. +

+

+ Checks whether an octet sequence starts with a UTF-8 byte order mark (BOM) +

+
+template <typename octet_iterator> 
+bool starts_with_bom (octet_iterator it, octet_iterator end);
+
+

+ octet_iterator: an input iterator.
+ it: beginning of the octet sequence to check
+ end: pass-end of the sequence to check
+ Return value: true if the sequence + starts with a UTF-8 byte order mark; false if not. +

+

+ Example of use: +

+
+unsigned char byte_order_mark[] = {0xef, 0xbb, 0xbf};
+bool bbom = starts_with_bom(byte_order_mark, byte_order_mark + sizeof(byte_order_mark));
+assert (bbom == true);
+
+

+ The typical use of this function is to check the first three bytes of a file. If + they form the UTF-8 BOM, we want to skip them before processing the actual UTF-8 + encoded text. +

+

+ utf8::is_bom +

+

+ Available in version 1.0 and later. Deprecated in version 2.3. starts_with_bom() should be used + instead. +

+

+ Checks whether a sequence of three octets is a UTF-8 byte order mark (BOM) +

+
+template <typename octet_iterator> 
+bool is_bom (octet_iterator it);  // Deprecated
+
+

+ octet_iterator: an input iterator.
+ it: beginning of the 3-octet sequence to check
+ Return value: true if the sequence + is UTF-8 byte order mark; false if not. +

+

+ Example of use: +

+
+unsigned char byte_order_mark[] = {0xef, 0xbb, 0xbf};
+bool bbom = is_bom(byte_order_mark);
+assert (bbom == true);
+
+

+ The typical use of this function is to check the first three bytes of a file. If + they form the UTF-8 BOM, we want to skip them before processing the actual UTF-8 + encoded text. +

+

+ If a sequence is + shorter than three bytes, an invalid iterator will be dereferenced. Therefore, this function is deprecated + in favor of starts_with_bom()that takes the end of sequence as an argument. +

+

+ Types From utf8 Namespace +

+

utf8::exception +

+

+ Available in version 2.3 and later. +

+

+ Base class for the exceptions thrown by UTF CPP library functions. +

+
+class exception : public std::exception {};
+
+

+ Example of use: +

+
+try {
+  code_that_uses_utf_cpp_library();
+}
+catch(const utf8::exception& utfcpp_ex) {
+  cerr << utfcpp_ex.what();
+}
+
+ +

utf8::invalid_code_point +

+

+ Available in version 1.0 and later. +

+

+ Thrown by UTF8 CPP functions such as advance and next if an UTF-8 sequence represents and invalid code point. +

+ +
+class invalid_code_point : public exception {
+public: 
+    uint32_t code_point() const;
+};
+
+
+

+ Member function code_point() can be used to determine the invalid code point that + caused the exception to be thrown. +

+

utf8::invalid_utf8 +

+

+ Available in version 1.0 and later. +

+

+ Thrown by UTF8 CPP functions such as next and prior if an invalid UTF-8 sequence + is detected during decoding. +

+ +
+class invalid_utf8 : public exception {
+public: 
+    uint8_t utf8_octet() const;
+};
+
+ +

+ Member function utf8_octet() can be used to determine the beginning of the byte + sequence that caused the exception to be thrown. +

+ +

utf8::invalid_utf16 +

+

+ Available in version 1.0 and later. +

+

+ Thrown by UTF8 CPP function utf16to8 if an invalid UTF-16 sequence + is detected during decoding. +

+ +
+class invalid_utf16 : public exception {
+public: 
+    uint16_t utf16_word() const;
+};
+
+ +

+ Member function utf16_word() can be used to determine the UTF-16 code unit + that caused the exception to be thrown. +

+

utf8::not_enough_room +

+

+ Available in version 1.0 and later. +

+

+ Thrown by UTF8 CPP functions such as next if the end of the decoded UTF-8 sequence + was reached before the code point was decoded. +

+ +
+class not_enough_room : public exception {};
+
+

+ utf8::iterator +

+

+ Available in version 2.0 and later. +

+

+ Adapts the underlying octet iterator to iterate over the sequence of code points, + rather than raw octets. +

+
+template <typename octet_iterator>
+class iterator;
+
+ +
Member functions
+
+
iterator();
the deafult constructor; the underlying octet_iterator is + constructed with its default constructor. +
explicit iterator (const octet_iterator& octet_it, + const octet_iterator& range_start, + const octet_iterator& range_end);
a constructor + that initializes the underlying octet_iterator with octet_it + and sets the range in which the iterator is considered valid. +
octet_iterator base () const;
returns the + underlying octet_iterator. +
uint32_t operator * () const;
decodes the utf-8 sequence + the underlying octet_iterator is pointing to and returns the code point. +
bool operator == (const iterator& rhs) + const;
returns true + if the two underlaying iterators are equal. +
bool operator != (const iterator& rhs) + const;
returns true + if the two underlaying iterators are not equal. +
iterator& operator ++ ();
the prefix increment - moves + the iterator to the next UTF-8 encoded code point. +
iterator operator ++ (int);
+ the postfix increment - moves the iterator to the next UTF-8 encoded code point and returns the current one. +
iterator& operator -- ();
the prefix decrement - moves + the iterator to the previous UTF-8 encoded code point. +
iterator operator -- (int);
+ the postfix decrement - moves the iterator to the previous UTF-8 encoded code point and returns the current one. +
+

+ Example of use: +

+
+char* threechars = "\xf0\x90\x8d\x86\xe6\x97\xa5\xd1\x88";
+utf8::iterator<char*> it(threechars, threechars, threechars + 9);
+utf8::iterator<char*> it2 = it;
+assert (it2 == it);
+assert (*it == 0x10346);
+assert (*(++it) == 0x65e5);
+assert ((*it++) == 0x65e5);
+assert (*it == 0x0448);
+assert (it != it2);
+utf8::iterator<char*> endit (threechars + 9, threechars, threechars + 9);  
+assert (++it == endit);
+assert (*(--it) == 0x0448);
+assert ((*it--) == 0x0448);
+assert (*it == 0x65e5);
+assert (--it == utf8::iterator<char*>(threechars, threechars, threechars + 9));
+assert (*it == 0x10346);
+
+

+ The purpose of utf8::iterator adapter is to enable easy iteration as well as the use of STL + algorithms with UTF-8 encoded strings. Increment and decrement operators are implemented in terms of + utf8::next() and utf8::prior() functions. +

+

+ Note that utf8::iterator adapter is a checked iterator. It operates on the range specified in + the constructor; any attempt to go out of that range will result in an exception. Even the comparison operators + require both iterator object to be constructed against the same range - otherwise an exception is thrown. Typically, + the range will be determined by sequence container functions begin and end, i.e.: +

+
+std::string s = "example";
+utf8::iterator i (s.begin(), s.begin(), s.end());
+
+

+ Functions From utf8::unchecked Namespace +

+

+ utf8::unchecked::append +

+

+ Available in version 1.0 and later. +

+

+ Encodes a 32 bit code point as a UTF-8 sequence of octets and appends the sequence + to a UTF-8 string. +

+
+template <typename octet_iterator>
+octet_iterator append(uint32_t cp, octet_iterator result);
+   
+
+

+ cp: A 32 bit integer representing a code point to append to the + sequence.
+ result: An output iterator to the place in the sequence where to + append the code point.
+ Return value: An iterator pointing to the place + after the newly appended sequence. +

+

+ Example of use: +

+
+unsigned char u[5] = {0,0,0,0,0};
+unsigned char* end = unchecked::append(0x0448, u);
+assert (u[0] == 0xd1 && u[1] == 0x88 && u[2] == 0 && u[3] == 0 && u[4] == 0);
+
+

+ This is a faster but less safe version of utf8::append. It does not + check for validity of the supplied code point, and may produce an invalid UTF-8 + sequence. +

+

+ utf8::unchecked::next +

+

+ Available in version 1.0 and later. +

+

+ Given the iterator to the beginning of a UTF-8 sequence, it returns the code point + and moves the iterator to the next position. +

+
+template <typename octet_iterator>
+uint32_t next(octet_iterator& it);
+   
+
+

+ it: a reference to an iterator pointing to the beginning of an UTF-8 + encoded code point. After the function returns, it is incremented to point to the + beginning of the next code point.
+ Return value: the 32 bit representation of the + processed UTF-8 code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+int cp = unchecked::next(w);
+assert (cp == 0x65e5);
+assert (w == twochars + 3);
+
+

+ This is a faster but less safe version of utf8::next. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ utf8::unchecked::peek_next +

+

+ Available in version 2.1 and later. +

+

+ Given the iterator to the beginning of a UTF-8 sequence, it returns the code point. +

+
+template <typename octet_iterator>
+uint32_t peek_next(octet_iterator it);
+   
+
+

+ it: an iterator pointing to the beginning of an UTF-8 + encoded code point.
+ Return value: the 32 bit representation of the + processed UTF-8 code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+int cp = unchecked::peek_next(w);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ This is a faster but less safe version of utf8::peek_next. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ utf8::unchecked::prior +

+

+ Available in version 1.02 and later. +

+

+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it + decreases the iterator until it hits the beginning of the previous UTF-8 encoded + code point and returns the 32 bits representation of the code point. +

+
+template <typename octet_iterator>
+uint32_t prior(octet_iterator& it);
+   
+
+

+ it: a reference pointing to an octet within a UTF-8 encoded string. + After the function returns, it is decremented to point to the beginning of the + previous code point.
+ Return value: the 32 bit representation of the + previous code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars + 3;
+int cp = unchecked::prior (w);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ This is a faster but less safe version of utf8::prior. It does not + check for validity of the supplied UTF-8 sequence and offers no boundary checking. +

+

+ utf8::unchecked::previous (deprecated, see utf8::unchecked::prior) +

+

+ Deprecated in version 1.02 and later. +

+

+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it + decreases the iterator until it hits the beginning of the previous UTF-8 encoded + code point and returns the 32 bits representation of the code point. +

+
+template <typename octet_iterator>
+uint32_t previous(octet_iterator& it);
+   
+
+

+ it: a reference pointing to an octet within a UTF-8 encoded string. + After the function returns, it is decremented to point to the beginning of the + previous code point.
+ Return value: the 32 bit representation of the + previous code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars + 3;
+int cp = unchecked::previous (w);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ The reason this function is deprecated is just the consistency with the "checked" + versions, where prior should be used instead of previous. + In fact, unchecked::previous behaves exactly the same as + unchecked::prior +

+

+ This is a faster but less safe version of utf8::previous. It does not + check for validity of the supplied UTF-8 sequence and offers no boundary checking. +

+

+ utf8::unchecked::advance +

+

+ Available in version 1.0 and later. +

+

+ Advances an iterator by the specified number of code points within an UTF-8 + sequence. +

+
+template <typename octet_iterator, typename distance_type>
+void advance (octet_iterator& it, distance_type n);
+   
+
+

+ it: a reference to an iterator pointing to the beginning of an UTF-8 + encoded code point. After the function returns, it is incremented to point to the + nth following code point.
+ n: a positive integer that shows how many code points we want to + advance.
+

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+unchecked::advance (w, 2);
+assert (w == twochars + 5);
+
+

+ This function works only "forward". In case of a negative n, there is + no effect. +

+

+ This is a faster but less safe version of utf8::advance. It does not + check for validity of the supplied UTF-8 sequence and offers no boundary checking. +

+

+ utf8::unchecked::distance +

+

+ Available in version 1.0 and later. +

+

+ Given the iterators to two UTF-8 encoded code points in a seqence, returns the + number of code points between them. +

+
+template <typename octet_iterator>
+typename std::iterator_traits<octet_iterator>::difference_type distance (octet_iterator first, octet_iterator last);
+
+

+ first: an iterator to a beginning of a UTF-8 encoded code point.
+ last: an iterator to a "post-end" of the last UTF-8 encoded code + point in the sequence we are trying to determine the length. It can be the + beginning of a new code point, or not.
+ Return value the distance between the iterators, + in code points. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+size_t dist = utf8::unchecked::distance(twochars, twochars + 5);
+assert (dist == 2);
+
+

+ This is a faster but less safe version of utf8::distance. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ utf8::unchecked::utf16to8 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-16 encoded string to UTF-8. +

+
+template <typename u16bit_iterator, typename octet_iterator>
+octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result);
+   
+
+

+ start: an iterator pointing to the beginning of the UTF-16 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-16 encoded + string to convert.
+ result: an output iterator to the place in the UTF-8 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-8 string. +

+

+ Example of use: +

+
+unsigned short utf16string[] = {0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e};
+vector<unsigned char> utf8result;
+unchecked::utf16to8(utf16string, utf16string + 5, back_inserter(utf8result));
+assert (utf8result.size() == 10);    
+
+

+ This is a faster but less safe version of utf8::utf16to8. It does not + check for validity of the supplied UTF-16 sequence. +

+

+ utf8::unchecked::utf8to16 +

+

+ Available in version 1.0 and later. +

+

+ Converts an UTF-8 encoded string to UTF-16 +

+
+template <typename u16bit_iterator, typename octet_iterator>
+u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result);
+   
+
+

+ start: an iterator pointing to the beginning of the UTF-8 encoded + string to convert. < br /> end: an iterator pointing to + pass-the-end of the UTF-8 encoded string to convert.
+ result: an output iterator to the place in the UTF-16 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-16 string. +

+

+ Example of use: +

+
+char utf8_with_surrogates[] = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e";
+vector <unsigned short> utf16result;
+unchecked::utf8to16(utf8_with_surrogates, utf8_with_surrogates + 9, back_inserter(utf16result));
+assert (utf16result.size() == 4);
+assert (utf16result[2] == 0xd834);
+assert (utf16result[3] == 0xdd1e);
+
+

+ This is a faster but less safe version of utf8::utf8to16. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ utf8::unchecked::utf32to8 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-32 encoded string to UTF-8. +

+
+template <typename octet_iterator, typename u32bit_iterator>
+octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result);
+   
+
+

+ start: an iterator pointing to the beginning of the UTF-32 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-32 encoded + string to convert.
+ result: an output iterator to the place in the UTF-8 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-8 string. +

+

+ Example of use: +

+
+int utf32string[] = {0x448, 0x65e5, 0x10346, 0};
+vector<unsigned char> utf8result;
+utf32to8(utf32string, utf32string + 3, back_inserter(utf8result));
+assert (utf8result.size() == 9);
+
+

+ This is a faster but less safe version of utf8::utf32to8. It does not + check for validity of the supplied UTF-32 sequence. +

+

+ utf8::unchecked::utf8to32 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-8 encoded string to UTF-32. +

+
+template <typename octet_iterator, typename u32bit_iterator>
+u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result);
+   
+
+

+ start: an iterator pointing to the beginning of the UTF-8 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-8 encoded string + to convert.
+ result: an output iterator to the place in the UTF-32 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-32 string. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+vector<int> utf32result;
+unchecked::utf8to32(twochars, twochars + 5, back_inserter(utf32result));
+assert (utf32result.size() == 2);
+
+

+ This is a faster but less safe version of utf8::utf8to32. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ Types From utf8::unchecked Namespace +

+

+ utf8::iterator +

+

+ Available in version 2.0 and later. +

+

+ Adapts the underlying octet iterator to iterate over the sequence of code points, + rather than raw octets. +

+
+template <typename octet_iterator>
+class iterator;
+
+ +
Member functions
+
+
iterator();
the deafult constructor; the underlying octet_iterator is + constructed with its default constructor. +
explicit iterator (const octet_iterator& octet_it); +
a constructor + that initializes the underlying octet_iterator with octet_it +
octet_iterator base () const;
returns the + underlying octet_iterator. +
uint32_t operator * () const;
decodes the utf-8 sequence + the underlying octet_iterator is pointing to and returns the code point. +
bool operator == (const iterator& rhs) + const;
returns true + if the two underlaying iterators are equal. +
bool operator != (const iterator& rhs) + const;
returns true + if the two underlaying iterators are not equal. +
iterator& operator ++ ();
the prefix increment - moves + the iterator to the next UTF-8 encoded code point. +
iterator operator ++ (int);
+ the postfix increment - moves the iterator to the next UTF-8 encoded code point and returns the current one. +
iterator& operator -- ();
the prefix decrement - moves + the iterator to the previous UTF-8 encoded code point. +
iterator operator -- (int);
+ the postfix decrement - moves the iterator to the previous UTF-8 encoded code point and returns the current one. +
+

+ Example of use: +

+
+char* threechars = "\xf0\x90\x8d\x86\xe6\x97\xa5\xd1\x88";
+utf8::unchecked::iterator<char*> un_it(threechars);
+utf8::unchecked::iterator<char*> un_it2 = un_it;
+assert (un_it2 == un_it);
+assert (*un_it == 0x10346);
+assert (*(++un_it) == 0x65e5);
+assert ((*un_it++) == 0x65e5);
+assert (*un_it == 0x0448);
+assert (un_it != un_it2);
+utf8::::unchecked::iterator<char*> un_endit (threechars + 9);  
+assert (++un_it == un_endit);
+assert (*(--un_it) == 0x0448);
+assert ((*un_it--) == 0x0448);
+assert (*un_it == 0x65e5);
+assert (--un_it == utf8::unchecked::iterator<char*>(threechars));
+assert (*un_it == 0x10346);
+
+

+ This is an unchecked version of utf8::iterator. It is faster in many cases, but offers + no validity or range checks. +

+

+ Points of interest +

+

+ Design goals and decisions +

+

+ The library was designed to be: +

+
    +
  1. + Generic: for better or worse, there are many C++ string classes out there, and + the library should work with as many of them as possible. +
  2. +
  3. + Portable: the library should be portable both accross different platforms and + compilers. The only non-portable code is a small section that declares unsigned + integers of different sizes: three typedefs. They can be changed by the users of + the library if they don't match their platform. The default setting should work + for Windows (both 32 and 64 bit), and most 32 bit and 64 bit Unix derivatives. +
  4. +
  5. + Lightweight: follow the "pay only for what you use" guideline. +
  6. +
  7. + Unintrusive: avoid forcing any particular design or even programming style on the + user. This is a library, not a framework. +
  8. +
+

+ Alternatives +

+

+ In case you want to look into other means of working with UTF-8 strings from C++, + here is the list of solutions I am aware of: +

+
    +
  1. + ICU Library. It is very powerful, + complete, feature-rich, mature, and widely used. Also big, intrusive, + non-generic, and doesn't play well with the Standard Library. I definitelly + recommend looking at ICU even if you don't plan to use it. +
  2. +
  3. + C++11 language and library features. Still far from complete, and not widely + supported by compiler vendors. +
  4. +
  5. + Glib::ustring. + A class specifically made to work with UTF-8 strings, and also feel like + std::string. If you prefer to have yet another string class in your + code, it may be worth a look. Be aware of the licensing issues, though. +
  6. +
  7. + Platform dependent solutions: Windows and POSIX have functions to convert strings + from one encoding to another. That is only a subset of what my library offers, + but if that is all you need it may be good enough. +
  8. +
+ +
    +
  1. + The Unicode Consortium. +
  2. +
  3. + ICU Library. +
  4. +
  5. + UTF-8 at Wikipedia +
  6. +
  7. + UTF-8 and Unicode FAQ for + Unix/Linux +
  8. +
+ + diff --git a/thirdparty/assimp/include/assimp/.editorconfig b/thirdparty/assimp/include/assimp/.editorconfig deleted file mode 100644 index 9ea66423ad1..00000000000 --- a/thirdparty/assimp/include/assimp/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -# See for details - -[*.{h,hpp,inl}] -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_size = 4 -indent_style = space diff --git a/thirdparty/assimp/include/assimp/BaseImporter.h b/thirdparty/assimp/include/assimp/BaseImporter.h index 48dfc8ed8bd..55f7fe3754a 100644 --- a/thirdparty/assimp/include/assimp/BaseImporter.h +++ b/thirdparty/assimp/include/assimp/BaseImporter.h @@ -48,8 +48,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include +#include struct aiScene; struct aiImporterDesc; @@ -80,6 +82,10 @@ class IOStream; class ASSIMP_API BaseImporter { friend class Importer; +private: + /* Pushes state into importer for the importer scale */ + virtual void UpdateImporterScale( Importer* pImp ); + public: /** Constructor to be privately used by #Importer */ @@ -132,7 +138,7 @@ public: * a suitable response to the caller. */ aiScene* ReadFile( - const Importer* pImp, + Importer* pImp, const std::string& pFile, IOSystem* pIOHandler ); @@ -161,14 +167,65 @@ public: * some loader features. Importers must provide this information. */ virtual const aiImporterDesc* GetInfo() const = 0; + /** + * Will be called only by scale process when scaling is requested. + */ + virtual void SetFileScale(double scale) + { + fileScale = scale; + } + + virtual double GetFileScale() const + { + return fileScale; + } + + enum ImporterUnits { + M, + MM, + CM, + INCHES, + FEET + }; + + /** + * Assimp Importer + * unit conversions available + * if you need another measurment unit add it below. + * it's currently defined in assimp that we prefer meters. + * */ + std::map importerUnits = { + {ImporterUnits::M, 1}, + {ImporterUnits::CM, 0.01}, + {ImporterUnits::MM, 0.001}, + {ImporterUnits::INCHES, 0.0254}, + {ImporterUnits::FEET, 0.3048} + }; + + virtual void SetApplicationUnits( const ImporterUnits& unit ) + { + importerScale = importerUnits[unit]; + applicationUnits = unit; + } + + virtual const ImporterUnits& GetApplicationUnits() + { + return applicationUnits; + } + // ------------------------------------------------------------------- /** Called by #Importer::GetExtensionList for each loaded importer. * Take the extension list contained in the structure returned by * #GetInfo and insert all file extensions into the given set. * @param extension set to collect file extensions in*/ void GetExtensionList(std::set& extensions); + +protected: + ImporterUnits applicationUnits = ImporterUnits::M; + double importerScale = 1.0; + double fileScale = 1.0; + -protected: // ------------------------------------------------------------------- /** Imports the given file into the given scene structure. The diff --git a/thirdparty/assimp/include/assimp/config.h.in b/thirdparty/assimp/include/assimp/config.h.in index d08b929a10c..3a6379bf40f 100644 --- a/thirdparty/assimp/include/assimp/config.h.in +++ b/thirdparty/assimp/include/assimp/config.h.in @@ -142,7 +142,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @brief Specifies the maximum angle that may be between two vertex tangents * that their tangents and bi-tangents are smoothed. * - * This applies to the CalcTangentSpace-Step. TFvhe angle is specified + * This applies to the CalcTangentSpace-Step. The angle is specified * in degrees. The maximum value is 175. * Property type: float. Default value: 45 degrees */ @@ -999,6 +999,13 @@ enum aiComponent # define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f #endif // !! AI_DEBONE_THRESHOLD +#define AI_CONFIG_APP_SCALE_KEY "APP_SCALE_FACTOR" + +#if (!defined AI_CONFIG_APP_SCALE_KEY) +# define AI_CONFIG_APP_SCALE_KEY 1.0 +#endif // AI_CONFIG_APP_SCALE_KEY + + // ---------- All the Build/Compile-time defines ------------ /** @brief Specifies if double precision is supported inside assimp diff --git a/thirdparty/assimp/include/assimp/irrXMLWrapper.h b/thirdparty/assimp/include/assimp/irrXMLWrapper.h deleted file mode 100644 index ec8ee7c76e0..00000000000 --- a/thirdparty/assimp/include/assimp/irrXMLWrapper.h +++ /dev/null @@ -1,144 +0,0 @@ -/* -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 INCLUDED_AI_IRRXML_WRAPPER -#define INCLUDED_AI_IRRXML_WRAPPER - -// some long includes .... -#include -#include "IOStream.hpp" -#include "BaseImporter.h" -#include - -namespace Assimp { - -// --------------------------------------------------------------------------------- -/** @brief Utility class to make IrrXML work together with our custom IO system - * See the IrrXML docs for more details. - * - * Construct IrrXML-Reader in BaseImporter::InternReadFile(): - * @code - * // open the file - * std::unique_ptr file( pIOHandler->Open( pFile)); - * if( file.get() == NULL) { - * throw DeadlyImportError( "Failed to open file " + pFile + "."); - * } - * - * // generate a XML reader for it - * std::unique_ptr mIOWrapper( new CIrrXML_IOStreamReader( file.get())); - * mReader = irr::io::createIrrXMLReader( mIOWrapper.get()); - * if( !mReader) { - * ThrowException( "xxxx: Unable to open file."); - * } - * @endcode - **/ -class CIrrXML_IOStreamReader : public irr::io::IFileReadCallBack { -public: - - // ---------------------------------------------------------------------------------- - //! Construction from an existing IOStream - explicit CIrrXML_IOStreamReader(IOStream* _stream) - : stream (_stream) - , t (0) - { - - // Map the buffer into memory and convert it to UTF8. IrrXML provides its - // own conversion, which is merely a cast from uintNN_t to uint8_t. Thus, - // it is not suitable for our purposes and we have to do it BEFORE IrrXML - // gets the buffer. Sadly, this forces us to map the whole file into - // memory. - - data.resize(stream->FileSize()); - stream->Read(&data[0],data.size(),1); - - // Remove null characters from the input sequence otherwise the parsing will utterly fail - unsigned int size = 0; - unsigned int size_max = static_cast(data.size()); - for(unsigned int i = 0; i < size_max; i++) { - if(data[i] != '\0') { - data[size++] = data[i]; - } - } - data.resize(size); - - BaseImporter::ConvertToUTF8(data); - } - - // ---------------------------------------------------------------------------------- - //! Virtual destructor - virtual ~CIrrXML_IOStreamReader() {} - - // ---------------------------------------------------------------------------------- - //! Reads an amount of bytes from the file. - /** @param buffer: Pointer to output buffer. - * @param sizeToRead: Amount of bytes to read - * @return Returns how much bytes were read. */ - virtual int read(void* buffer, int sizeToRead) { - if(sizeToRead<0) { - return 0; - } - if(t+sizeToRead>data.size()) { - sizeToRead = static_cast(data.size()-t); - } - - memcpy(buffer,&data.front()+t,sizeToRead); - - t += sizeToRead; - return sizeToRead; - } - - // ---------------------------------------------------------------------------------- - //! Returns size of file in bytes - virtual int getSize() { - return (int)data.size(); - } - -private: - IOStream* stream; - std::vector data; - size_t t; - -}; // ! class CIrrXML_IOStreamReader - -} // ! Assimp - -#endif // !! INCLUDED_AI_IRRXML_WRAPPER diff --git a/thirdparty/assimp/include/assimp/scene.h b/thirdparty/assimp/include/assimp/scene.h index df5d6f3b5e0..2667db85b3b 100644 --- a/thirdparty/assimp/include/assimp/scene.h +++ b/thirdparty/assimp/include/assimp/scene.h @@ -56,9 +56,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "material.h" #include "anim.h" #include "metadata.h" -#include #ifdef __cplusplus +# include extern "C" { #endif