diff options
Diffstat (limited to 'modules/assimp')
-rw-r--r-- | modules/assimp/editor_scene_importer_assimp.cpp | 1595 | ||||
-rw-r--r-- | modules/assimp/editor_scene_importer_assimp.h | 147 | ||||
-rw-r--r-- | modules/assimp/import_state.h | 115 | ||||
-rw-r--r-- | modules/assimp/import_utils.h | 448 |
4 files changed, 1144 insertions, 1161 deletions
diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index 05f9120a07..e5439fd132 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 <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 <zutil.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/LogStream.hpp> +#include <assimp/Logger.hpp> #include <string> void EditorSceneImporterAssimp::get_extensions(List<String> *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<float> &p_times, co ERR_FAIL_V(p_values[0]); } -void EditorSceneImporterAssimp::_generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<String, Transform> &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<String, int>::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<String, int> &ownership, Map<int, int> &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector<SkeletonHole> &p_holes, const Map<String, Transform> &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<SkeletonHole> 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<String, int>::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<SkeletonHole>(), bind_xforms); - } -} - -void EditorSceneImporterAssimp::_generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, const Map<String, Transform> &bind_xforms) { - - //find skeletons at this level, there may be multiple root nodes for each - Map<int, List<aiNode *> > 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<aiNode *>(); - } - skeletons_found[skeleton].push_back(p_assimp_node->mChildren[i]); - } - } - - //go via the potential skeletons found and generate the actual skeleton - for (Map<int, List<aiNode *> >::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<aiNode *>::Element *F = E->get().front(); F; F = F->next()) { - _fill_node_relationships(state, F->get(), ownership, skeleton_map, E->key(), skeleton, "", holecount, Vector<SkeletonHole>(), 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<String, Transform> bind_xforms; //temporary map to store bind transforms - //guess the skeletons, since assimp does not really support them directly - Map<String, int> 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<int, int> 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<MeshInstance *, Skeleton *>::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<Skeleton *, const Spatial *>::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<Skeleton *, const Spatial *>::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<Texture> EditorSceneImporterAssimp::_load_texture(ImportState &state, String p_path) { - Vector<String> split_path = p_path.get_basename().split("*"); - if (split_path.size() == 2) { - size_t texture_idx = split_path[1].to_int(); - ERR_FAIL_COND_V(texture_idx >= state.assimp_scene->mNumTextures, Ref<Texture>()); - 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<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - - Ref<ImageTexture> t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } else if (tex->CheckFormat("jpg")) { - Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - Ref<ImageTexture> 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<Texture>(), "Open Asset Import: Embedded dds not implemented."); - //Ref<Image> img = Image::_dds_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - //ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - //Ref<ImageTexture> t; - //t.instance(); - //t->create_from_image(img); - //t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - //return t; - } - } else { - Ref<Image> img; - img.instance(); - PoolByteArray arr; - uint32_t size = tex->mWidth * tex->mHeight; - arr.resize(size); - memcpy(arr.write().ptr(), tex->pcData, size); - ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Texture>()); - //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<Texture>()); - - Ref<ImageTexture> t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } - return Ref<Texture>(); - } - Ref<Texture> p_texture = ResourceLoader::load(p_path, "Texture"); - return p_texture; -} - -Ref<Material> 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<Material>()); - - aiMaterial *ai_material = state.assimp_scene->mMaterials[p_index]; - Ref<SpatialMaterial> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &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<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( + ImportState &state, + const Vector<int> &p_surface_indices, + const aiNode *assimp_node, + Skeleton *p_skeleton) { Ref<ArrayMesh> 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<Mesh> 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<Mesh> 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<BoneInfo>(); @@ -1255,23 +576,34 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS } } + // + // Create mesh from data from assimp + // + Ref<SurfaceTool> st; st.instance(); st->begin(Mesh::PRIMITIVE_TRIANGLES); for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { + + // Get the texture coordinates if they exist if (ai_mesh->HasTextureCoords(0)) { has_uvs = true; st->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<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS } } + // We have vertex weights right? if (vertex_weights.has(j)) { Vector<BoneInfo> bone_info = vertex_weights[j]; @@ -1293,6 +626,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS bones.resize(bone_info.size()); Vector<float> weights; weights.resize(bone_info.size()); + + // todo? do we really need to loop over all bones? - assimp may have helper to find all influences on this vertex. for (int k = 0; k < bone_info.size(); k++) { bones.write[k] = bone_info[k].bone; weights.write[k] = bone_info[k].weight; @@ -1302,30 +637,152 @@ Ref<Mesh> 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<size_t> 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> material; + aiMaterial *ai_material = state.assimp_scene->mMaterials[ai_mesh->mMaterialIndex]; + Ref<SpatialMaterial> 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 = 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<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_normal, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(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<Image> texture; + AssimpImageData image_data; + + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_emissive, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(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<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_specular, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, image_data.texture); + } + } - if (!state.material_cache.has(ai_mesh->mMaterialIndex)) { - material = _generate_material_from_index(state, ai_mesh->mMaterialIndex, p_double_sided_material); + aiTextureType tex_roughness = aiTextureType_SHININESS; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_roughness, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, image_data.texture); + } } Array array_mesh = st->commit_to_arrays(); @@ -1335,16 +792,13 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS Map<uint32_t, String> 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<Mesh> 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<Mesh> 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<Mesh> 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<Mesh> 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<Mesh> 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]; - Spatial *new_node = NULL; - String node_name = _assimp_get_string(p_assimp_node->mName); - Transform node_transform = _assimp_matrix_transform(p_assimp_node->mTransformation); - - if (p_assimp_node->mNumMeshes > 0) { - /* MESH NODE */ - Ref<Mesh> mesh; - Skeleton *skeleton = NULL; - { + // iterate over all the bones on the mesh for this node only! + for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) { - //see if we have mesh cache for this. - Vector<int> 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); - - //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]); + aiBone *bone = mesh->mBones[boneIndex]; + if (bone->mName == bone_name) { + printf("matched bone by name: %s\n", bone->mName.C_Str()); + return bone; } + } + } - 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; - } + return NULL; +} - mesh = _generate_mesh_from_surface_indices(state, surface_indices, skeleton, double_sided_material); - state.mesh_cache[mesh_key] = mesh; +/** + * 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> mesh; + Skeleton *skeleton = NULL; + // see if we have mesh cache for this. + Vector<int> 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<aiBone*, Skeleton*> // 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<const aiBone *, Skeleton *>::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. } - - mesh = state.mesh_cache[mesh_key]; } + surface_indices.push_back(mesh_index); + } - MeshInstance *mesh_node = memnew(MeshInstance); - if (skeleton) { - state.mesh_skeletons[mesh_node] = skeleton; + surface_indices.sort(); + String mesh_key; + for (int i = 0; i < surface_indices.size(); i++) { + if (i > 0) { + mesh_key += ":"; } - 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); + mesh_key += itos(surface_indices[i]); + } - 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(); + 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 light_transform; - light_transform.set_look_at(pos, pos + dir, up); + //Transform transform = recursive_state.node_transform; - node_transform *= light_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<Skeleton *, const Spatial *>::Element *match = state.armature_skeletons.find(skeleton); + node_transform = match->value()->get_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; + MeshInstance *mesh_node = memnew(MeshInstance); + mesh = state.mesh_cache[mesh_key]; + mesh_node->set_mesh(mesh); - node_transform *= xform; + attach_new_node(state, + mesh_node, + assimp_node, + parent_node, + node_name, + node_transform); - light->set_transform(xform); + // set this once and for all + if (skeleton != NULL) { + // root must be informed of its new child + parent_node->add_child(skeleton); - //light->set_param(Light::PARAM_ATTENUATION, 1); - } else if (ai_light->mType == aiLightSource_SPOT) { - light = memnew(SpotLight); + // owner must be set after adding to tree + skeleton->set_owner(state.root); - 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(); + skeleton->set_transform(node_transform); - Transform light_transform; - light_transform.set_look_at(pos, pos + dir, up); - node_transform *= light_transform; + // must be done after added to tree + mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton)); + } +} - //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; - } else if (state.camera_cache.has(node_name)) { +/** 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<const aiNode *, const Node *>::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(); - aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[node_name]]; - ERR_FAIL_COND(!ai_camera); + ERR_CONTINUE(assimp_node == NULL); + ERR_CONTINUE(parent_node == NULL); - Camera *camera = memnew(Camera); + String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); + Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); - float near = ai_camera->mClipPlaneNear; - if (Math::is_equal_approx(near, 0.0f)) { - near = 0.1f; + if (assimp_node->mNumMeshes > 0) { + create_mesh(state, assimp_node, node_name, current_node, parent_node, node_transform); } - 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); +/** + * 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.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()); - } + xform.origin = pos; - skeleton->localize_rests(); - node_name = "Skeleton"; //don't use the bone root name - node_transform = Transform(); //don't transform + recursive_state.node_transform *= xform; - new_node = skeleton; - } else { - //generic node - new_node = memnew(Spatial); - } + light->set_transform(xform); - { + //light->set_param(Light::PARAM_ATTENUATION, 1); + } else if (ai_light->mType == aiLightSource_SPOT) { + light = memnew(SpotLight); - new_node->set_name(node_name); - new_node->set_transform(node_transform); - p_parent->add_child(new_node); - new_node->set_owner(state.root); - } + 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(); - state.node_map[node_name] = new_node; + Transform light_transform; + light_transform.set_look_at(pos, pos + dir, up); + recursive_state.node_transform *= light_transform; - for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { - _generate_node(state, p_assimp_node->mChildren[i], new_node); + //light->set_param(Light::PARAM_ATTENUATION, 0.0f); } -} + ERR_FAIL_COND(light == NULL); -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; + 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); } -void EditorSceneImporterAssimp::_set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> 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; +/** + * 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; } - texture->set_flags(flags); -} + camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar); -void EditorSceneImporterAssimp::_find_texture_path(const String &r_p_path, String &r_path, bool &r_found) { + 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); - _Directory dir; + Transform xform; + xform.set_look_at(pos, look_at, up); - List<String> exts; - ImageLoader::get_recognized_extensions(&exts); + recursive_state.new_node = camera; - Vector<String> split_path = r_path.get_basename().split("*"); - if (split_path.size() == 2) { - r_found = true; - return; - } + attach_new_node(state, + recursive_state.new_node, + recursive_state.assimp_node, + recursive_state.parent_node, + recursive_state.node_name, + recursive_state.node_transform); +} - 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; - } +/** + * 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); - 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 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<Skeleton *, const Spatial *>::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; } } -} -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; - } + // 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); - 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; - } + ERR_FAIL_COND(state.root == NULL); + ERR_FAIL_COND(recursive_state.skeleton == NULL); - 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; - } -} + 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<Spatial>(recursive_state.parent_node)); -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; + //skeleton->set_use_bones_in_world_transform(true); + print_verbose("Created new FBX skeleton for armature node"); } - name = name.replace(".", ""); //can break things, specially bone names + ERR_FAIL_COND_MSG(recursive_state.skeleton == NULL, "Mesh has invalid armature detection - report this"); - return name; -} + // this transform is a bone + recursive_state.skeleton->add_bone(recursive_state.node_name); -String EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString &p_string) const { + 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); - 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; + 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); } - 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; -} +/** + * 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); -Ref<Animation> EditorSceneImporterAssimp::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { - return Ref<Animation>(); -} + Spatial *new_node = NULL; + String node_name = AssimpUtils::get_assimp_string(assimp_node->mName); + Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation); -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; -} + // can safely return null - is this node a bone? + aiBone *bone = get_bone_by_name(state.assimp_scene, assimp_node->mName); + + // 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); + + // Creation code + if (state.light_cache.has(node_name)) { + create_light(state, recursive_state); + } else if (state.camera_cache.has(node_name)) { + create_camera(state, recursive_state); + } else if (bone != NULL) { + create_bone(state, recursive_state); + } else { + //generic node + 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); + } + + // 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); + } +}
\ 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 7a30816e3b..787376c9af 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 <assimp/matrix4x4.h> +#include <assimp/scene.h> +#include <assimp/types.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/LogStream.hpp> +#include <assimp/Logger.hpp> + +#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<String, Ref<Mesh> > mesh_cache; - Map<int, Ref<Material> > material_cache; - Map<String, int> light_cache; - Map<String, int> camera_cache; - Vector<Skeleton *> skeletons; - Map<String, int> bone_owners; //maps bones to skeleton index owned by - Map<String, Node *> node_map; - Map<MeshInstance *, Skeleton *> 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> 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<Texture> _load_texture(ImportState &state, String p_path); - Ref<Material> _generate_material_from_index(ImportState &state, int p_index, bool p_double_sided); - Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &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<String, int> &ownership, Map<String, Transform> &bind_xforms); - void _fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector<SkeletonHole> &p_holes, const Map<String, Transform> &bind_xforms); - void _generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, const Map<String, Transform> &bind_xforms); + Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &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> 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<String> *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<String> *r_missing_deps, Error *r_err = NULL); - virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps); + Ref<Image> 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 0000000000..8d82cd3e39 --- /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 <assimp/matrix4x4.h> +#include <assimp/scene.h> +#include <assimp/types.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/LogStream.hpp> +#include <assimp/Logger.hpp> + +namespace AssimpImporter { +/** Import state is for global scene import data + * This makes the code simpler and contains useful lookups. + */ +struct ImportState { + + String path; + const aiScene *assimp_scene; + uint32_t max_bone_weights; + + Spatial *root; + Map<String, Ref<Mesh> > mesh_cache; + Map<int, Ref<Material> > material_cache; + Map<String, int> light_cache; + Map<String, int> camera_cache; + //Vector<Skeleton *> skeletons; + Map<Skeleton *, const Spatial *> armature_skeletons; // maps skeletons based on their armature nodes. + Map<const aiBone *, Skeleton *> bone_to_skeleton_lookup; // maps bones back into their skeleton + // very useful for when you need to ask assimp for the bone mesh + Map<String, Node *> node_map; + Map<const aiNode *, const Node *> assimp_node_map; + Map<String, Ref<Image> > path_to_image_cache; + bool fbx; //for some reason assimp does some things different for FBX + AnimationPlayer *animation_player; +}; + +struct AssimpImageData { + Ref<Image> raw_image; + Ref<ImageTexture> 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 0000000000..4be76ade0f --- /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 <assimp/SceneCombiner.h> +#include <assimp/cexport.h> +#include <assimp/cimport.h> +#include <assimp/matrix4x4.h> +#include <assimp/pbrmaterial.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/LogStream.hpp> +#include <assimp/Logger.hpp> +#include <string> + +using namespace AssimpImporter; + +#define AI_PROPERTIES aiTextureType_UNKNOWN, 0 +#define AI_NULL 0, 0 +#define AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR "$raw.Maya|baseColor" +#define AI_MATKEY_FBX_MAYA_METALNESS_FACTOR "$raw.Maya|metalness" +#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR "$raw.Maya|diffuseRoughness" + +#define AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE "$raw.Maya|emissionColor|file" +#define AI_MATKEY_FBX_MAYA_EMISSIVE_FACTOR "$raw.Maya|emission" +#define AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE "$raw.Maya|metalness|file" +#define AI_MATKEY_FBX_MAYA_METALNESS_UV_XFORM "$raw.Maya|metalness|uvtrafo" +#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE "$raw.Maya|diffuseRoughness|file" +#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_UV_XFORM "$raw.Maya|diffuseRoughness|uvtrafo" +#define AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE "$raw.Maya|baseColor|file" +#define AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM "$raw.Maya|baseColor|uvtrafo" +#define AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE "$raw.Maya|normalCamera|file" +#define AI_MATKEY_FBX_MAYA_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo" + +#define AI_MATKEY_FBX_NORMAL_TEXTURE "$raw.Maya|normalCamera|file" +#define AI_MATKEY_FBX_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo" + +#define AI_MATKEY_FBX_MAYA_STINGRAY_DISPLACEMENT_SCALING_FACTOR "$raw.Maya|displacementscaling" +#define AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR "$raw.Maya|base_color" +#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR "$raw.Maya|emissive" +#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR "$raw.Maya|metallic" +#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR "$raw.Maya|roughness" +#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR "$raw.Maya|emissive_intensity" + +#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE "$raw.Maya|TEX_normal_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_UV_XFORM "$raw.Maya|TEX_normal_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE "$raw.Maya|TEX_color_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM "$raw.Maya|TEX_color_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE "$raw.Maya|TEX_metallic_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_UV_XFORM "$raw.Maya|TEX_metallic_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE "$raw.Maya|TEX_roughness_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_UV_XFORM "$raw.Maya|TEX_roughness_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE "$raw.Maya|TEX_emissive_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_UV_XFORM "$raw.Maya|TEX_emissive_map|uvtrafo" +#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_TEXTURE "$raw.Maya|TEX_ao_map|file" +#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_UV_XFORM "$raw.Maya|TEX_ao_map|uvtrafo" + +/** + * Assimp Utils + * Conversion tools / glue code to convert from assimp to godot +*/ +class AssimpUtils { +public: + /** + * calculate tangents for mesh data from assimp data + */ + static void calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, 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<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { + return Ref<Animation>(); + } + + /** + * Converts aiMatrix4x4 to godot Transform + */ + static const Transform assimp_matrix_transform(const aiMatrix4x4 p_matrix) { + aiMatrix4x4 matrix = p_matrix; + Transform xform; + xform.set(matrix.a1, matrix.a2, matrix.a3, matrix.b1, matrix.b2, matrix.b3, matrix.c1, matrix.c2, matrix.c3, matrix.a4, matrix.b4, matrix.c4); + return xform; + } + + /** Get fbx fps for time mode meta data + */ + static float get_fbx_fps(int32_t time_mode, const aiScene *p_scene) { + switch (time_mode) { + case AssetImportFbx::TIME_MODE_DEFAULT: return 24; //hack + case AssetImportFbx::TIME_MODE_120: return 120; + case AssetImportFbx::TIME_MODE_100: return 100; + case AssetImportFbx::TIME_MODE_60: return 60; + case AssetImportFbx::TIME_MODE_50: return 50; + case AssetImportFbx::TIME_MODE_48: return 48; + case AssetImportFbx::TIME_MODE_30: return 30; + case AssetImportFbx::TIME_MODE_30_DROP: return 30; + case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: return 29.9700262f; + case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: return 29.9700262f; + case AssetImportFbx::TIME_MODE_PAL: return 25; + case AssetImportFbx::TIME_MODE_CINEMA: return 24; + case AssetImportFbx::TIME_MODE_1000: return 1000; + case AssetImportFbx::TIME_MODE_CINEMA_ND: return 23.976f; + case AssetImportFbx::TIME_MODE_CUSTOM: + int32_t frame_rate = -1; + p_scene->mMetaData->Get("FrameRate", frame_rate); + return frame_rate; + } + return 0; + } + + /** + * Get global transform for the current node - so we can use world space rather than + * local space coordinates + * useful if you need global - although recommend using local wherever possible over global + * as you could break fbx scaling :) + */ + static Transform _get_global_assimp_node_transform(const aiNode *p_current_node) { + aiNode const *current_node = p_current_node; + Transform xform; + while (current_node != 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<String> paths; + paths.push_back(path.get_basename() + extension); + paths.push_back(path + extension); + paths.push_back(path); + paths.push_back(p_path.get_base_dir().plus_file(path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file(path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file(path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file())); + paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file().get_basename() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file() + extension)); + paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file())); + for (int i = 0; i < paths.size(); i++) { + if (dir.file_exists(paths[i])) { + found = true; + path = paths[i]; + return; + } + } + } + + /** find the texture path for the supplied fbx path inside godot + * very simple lookup for subfolders etc for a texture which may or may not be in a directory + */ + static void find_texture_path(const String &r_p_path, String &r_path, bool &r_found) { + _Directory dir; + + List<String> exts; + ImageLoader::get_recognized_extensions(&exts); + + Vector<String> split_path = r_path.get_basename().split("*"); + if (split_path.size() == 2) { + r_found = true; + return; + } + + if (dir.file_exists(r_p_path.get_base_dir() + r_path.get_file())) { + r_path = r_p_path.get_base_dir() + r_path.get_file(); + r_found = true; + return; + } + + for (int32_t i = 0; i < exts.size(); i++) { + if (r_found) { + return; + } + 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<ImageTexture> 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<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path) { + + Map<String, Ref<Image> >::Element *match = state.path_to_image_cache.find(p_path); + + // if our cache contains this image then don't bother + if (match) { + return match->get(); + } + + Vector<String> split_path = p_path.get_basename().split("*"); + if (split_path.size() == 2) { + size_t texture_idx = split_path[1].to_int(); + ERR_FAIL_COND_V(texture_idx >= p_scene->mNumTextures, Ref<Image>()); + aiTexture *tex = p_scene->mTextures[texture_idx]; + String filename = AssimpUtils::get_raw_string_from_assimp(tex->mFilename); + filename = filename.get_file(); + print_verbose("Open Asset Import: Loading embedded texture " + filename); + if (tex->mHeight == 0) { + if (tex->CheckFormat("png")) { + Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + state.path_to_image_cache.insert(p_path, img); + return img; + } else if (tex->CheckFormat("jpg")) { + Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + state.path_to_image_cache.insert(p_path, img); + return img; + } else if (tex->CheckFormat("dds")) { + ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented"); + ERR_FAIL_COND_V(true, Ref<Image>()); + } + } else { + Ref<Image> img; + img.instance(); + PoolByteArray arr; + uint32_t size = tex->mWidth * tex->mHeight; + arr.resize(size); + memcpy(arr.write().ptr(), tex->pcData, size); + ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Image>()); + //ARGB8888 to RGBA8888 + for (int32_t i = 0; i < arr.size() / 4; i++) { + arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0]; + arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1]; + arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2]; + arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3]; + } + img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr); + ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); + state.path_to_image_cache.insert(p_path, img); + return img; + } + return Ref<Image>(); + } else { + Ref<Texture> texture = ResourceLoader::load(p_path); + Ref<Image> image = texture->get_data(); + state.path_to_image_cache.insert(p_path, image); + return image; + } + + return Ref<Image>(); + } + + /* create texture from assimp data, if found in path */ + static bool CreateAssimpTexture( + AssimpImporter::ImportState &state, + aiString texture_path, + String &filename, + String &path, + AssimpImageData &image_state) { + filename = get_raw_string_from_assimp(texture_path); + path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); + bool found = false; + find_texture_path(state.path, path, found); + if (found) { + image_state.raw_image = AssimpUtils::load_image(state, state.assimp_scene, path); + if (image_state.raw_image.is_valid()) { + image_state.texture.instance(); + image_state.texture->create_from_image(image_state.raw_image); + image_state.texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); + return true; + } + } + + return false; + } + /** GetAssimpTexture + * Designed to retrieve textures for you + */ + static bool GetAssimpTexture( + AssimpImporter::ImportState &state, + aiMaterial *ai_material, + aiTextureType texture_type, + String &filename, + String &path, + AssimpImageData &image_state) { + aiString ai_filename = aiString(); + if (AI_SUCCESS == ai_material->GetTexture(texture_type, 0, &ai_filename, NULL, NULL, NULL, NULL, image_state.map_mode)) { + return CreateAssimpTexture(state, ai_filename, filename, path, image_state); + } + + return false; + } +}; + +#endif // IMPORT_UTILS_IMPORTER_ASSIMP_H |