diff options
Diffstat (limited to 'modules')
54 files changed, 1835 insertions, 1424 deletions
diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index 05f9120a07..f2f51d9dd3 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,41 @@ 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) { +// +// Mesh Generation from indices ? why do we need so much mesh code +// [debt needs looked into] +Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices( + ImportState &state, + const Vector<int> &p_surface_indices, + const aiNode *assimp_node, + Skeleton *p_skeleton) { - 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; + Ref<ArrayMesh> mesh; + mesh.instance(); + bool has_uvs = false; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); - } - } - } + Map<String, uint32_t> morph_mesh_string_lookup; - { - 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); - } - } + for (int i = 0; i < p_surface_indices.size(); i++) { + const unsigned int mesh_idx = p_surface_indices[0]; + const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_idx]; + for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) { - 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); + String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName); + if (!morph_mesh_string_lookup.has(ai_anim_mesh_name)) { + morph_mesh_string_lookup.insert(ai_anim_mesh_name, j); + mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); + if (ai_anim_mesh_name.empty()) { + ai_anim_mesh_name = String("morph_") + itos(j); } - } - } 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); + mesh->add_blend_shape(ai_anim_mesh_name); } } } - 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) { - - 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 +572,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 +585,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 +595,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 +637,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 +645,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,47 +656,239 @@ 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 base_color = aiTextureType_BASE_COLOR; + { + String filename, path; + AssimpImageData image_data; + + if (AssimpUtils::GetAssimpTexture(state, ai_material, base_color, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + + // anything transparent must be culled + if (image_data.raw_image->detect_alpha() != Image::ALPHA_NONE) { + mat->set_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); + } + } + + 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_normal_camera = aiTextureType_NORMAL_CAMERA; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_normal_camera, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture); + } + } + + aiTextureType tex_emission_color = aiTextureType_EMISSION_COLOR; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_emission_color, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture); + } + } + + aiTextureType tex_metalness = aiTextureType_METALNESS; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_metalness, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, image_data.texture); + } + } + + aiTextureType tex_roughness = aiTextureType_DIFFUSE_ROUGHNESS; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_roughness, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, 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_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); + } + } + + aiTextureType tex_ao_map = aiTextureType_AMBIENT_OCCLUSION; + { + String filename, path; + Ref<ImageTexture> texture; + AssimpImageData image_data; + + // Process texture normal map + if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_ao_map, filename, path, image_data)) { + AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture); + mat->set_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION, true); + mat->set_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION, image_data.texture); + } } Array array_mesh = st->commit_to_arrays(); Array morphs; morphs.resize(ai_mesh->mNumAnimMeshes); Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES; - 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); + + if (ai_anim_mesh_name.empty()) { + ai_anim_mesh_name = String("morph_") + itos(j); } Array array_copy; @@ -1363,12 +909,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 +927,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 +939,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 +957,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 +967,387 @@ 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) { - - Spatial *new_node = NULL; - String node_name = _assimp_get_string(p_assimp_node->mName); - Transform node_transform = _assimp_matrix_transform(p_assimp_node->mTransformation); +/* 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]; - 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); + + light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b)); + recursive_state.new_node = light; -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; + 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. -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; + state.armature_skeletons.insert(recursive_state.skeleton, Object::cast_to<Spatial>(recursive_state.parent_node)); + + //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); + + //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 EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString &p_string) const { + 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()); - 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; + // 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..56d89ffea7 --- /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[2]; +}; + +/** 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 diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 5a32fa1c2c..8708430257 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -63,139 +63,137 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, ERR_FAIL_V(ERR_UNAVAILABLE); } - if (err == OK) { - // Image data (might be indexed) - PoolVector<uint8_t> data; - int data_len = 0; + // Image data (might be indexed) + PoolVector<uint8_t> data; + int data_len = 0; - if (bits_per_pixel <= 8) { // indexed - data_len = width * height; - } else { // color - data_len = width * height * 4; - } - ERR_FAIL_COND_V(data_len == 0, ERR_BUG); - err = data.resize(data_len); - - PoolVector<uint8_t>::Write data_w = data.write(); - uint8_t *write_buffer = data_w.ptr(); - - const uint32_t width_bytes = width * bits_per_pixel / 8; - const uint32_t line_width = (width_bytes + 3) & ~3; - - // The actual data traversal is determined by - // the data width in case of 8/4/1 bit images - const uint32_t w = bits_per_pixel >= 24 ? width : width_bytes; - const uint8_t *line = p_buffer + (line_width * (height - 1)); - - for (unsigned int i = 0; i < height; i++) { - const uint8_t *line_ptr = line; - - for (unsigned int j = 0; j < w; j++) { - switch (bits_per_pixel) { - case 1: { - uint8_t color_index = *line_ptr; - - write_buffer[index + 0] = (color_index >> 7) & 1; - write_buffer[index + 1] = (color_index >> 6) & 1; - write_buffer[index + 2] = (color_index >> 5) & 1; - write_buffer[index + 3] = (color_index >> 4) & 1; - write_buffer[index + 4] = (color_index >> 3) & 1; - write_buffer[index + 5] = (color_index >> 2) & 1; - write_buffer[index + 6] = (color_index >> 1) & 1; - write_buffer[index + 7] = (color_index >> 0) & 1; - - index += 8; - line_ptr += 1; - } break; - case 4: { - uint8_t color_index = *line_ptr; - - write_buffer[index + 0] = (color_index >> 4) & 0x0f; - write_buffer[index + 1] = color_index & 0x0f; - - index += 2; - line_ptr += 1; - } break; - case 8: { - uint8_t color_index = *line_ptr; - - write_buffer[index] = color_index; - - index += 1; - line_ptr += 1; - } break; - case 24: { - uint32_t color = *((uint32_t *)line_ptr); - - write_buffer[index + 2] = color & 0xff; - write_buffer[index + 1] = (color >> 8) & 0xff; - write_buffer[index + 0] = (color >> 16) & 0xff; - write_buffer[index + 3] = 0xff; - - index += 4; - line_ptr += 3; - } break; - case 32: { - uint32_t color = *((uint32_t *)line_ptr); - - write_buffer[index + 2] = color & 0xff; - write_buffer[index + 1] = (color >> 8) & 0xff; - write_buffer[index + 0] = (color >> 16) & 0xff; - write_buffer[index + 3] = color >> 24; - - index += 4; - line_ptr += 4; - } break; - } + if (bits_per_pixel <= 8) { // indexed + data_len = width * height; + } else { // color + data_len = width * height * 4; + } + ERR_FAIL_COND_V(data_len == 0, ERR_BUG); + err = data.resize(data_len); + + PoolVector<uint8_t>::Write data_w = data.write(); + uint8_t *write_buffer = data_w.ptr(); + + const uint32_t width_bytes = width * bits_per_pixel / 8; + const uint32_t line_width = (width_bytes + 3) & ~3; + + // The actual data traversal is determined by + // the data width in case of 8/4/1 bit images + const uint32_t w = bits_per_pixel >= 24 ? width : width_bytes; + const uint8_t *line = p_buffer + (line_width * (height - 1)); + + for (uint64_t i = 0; i < height; i++) { + const uint8_t *line_ptr = line; + + for (unsigned int j = 0; j < w; j++) { + switch (bits_per_pixel) { + case 1: { + uint8_t color_index = *line_ptr; + + write_buffer[index + 0] = (color_index >> 7) & 1; + write_buffer[index + 1] = (color_index >> 6) & 1; + write_buffer[index + 2] = (color_index >> 5) & 1; + write_buffer[index + 3] = (color_index >> 4) & 1; + write_buffer[index + 4] = (color_index >> 3) & 1; + write_buffer[index + 5] = (color_index >> 2) & 1; + write_buffer[index + 6] = (color_index >> 1) & 1; + write_buffer[index + 7] = (color_index >> 0) & 1; + + index += 8; + line_ptr += 1; + } break; + case 4: { + uint8_t color_index = *line_ptr; + + write_buffer[index + 0] = (color_index >> 4) & 0x0f; + write_buffer[index + 1] = color_index & 0x0f; + + index += 2; + line_ptr += 1; + } break; + case 8: { + uint8_t color_index = *line_ptr; + + write_buffer[index] = color_index; + + index += 1; + line_ptr += 1; + } break; + case 24: { + uint32_t color = *((uint32_t *)line_ptr); + + write_buffer[index + 2] = color & 0xff; + write_buffer[index + 1] = (color >> 8) & 0xff; + write_buffer[index + 0] = (color >> 16) & 0xff; + write_buffer[index + 3] = 0xff; + + index += 4; + line_ptr += 3; + } break; + case 32: { + uint32_t color = *((uint32_t *)line_ptr); + + write_buffer[index + 2] = color & 0xff; + write_buffer[index + 1] = (color >> 8) & 0xff; + write_buffer[index + 0] = (color >> 16) & 0xff; + write_buffer[index + 3] = color >> 24; + + index += 4; + line_ptr += 4; + } break; } - line -= line_width; } + line -= line_width; + } - if (p_color_buffer == NULL || color_table_size == 0) { // regular pixels + if (p_color_buffer == NULL || color_table_size == 0) { // regular pixels - p_image->create(width, height, 0, Image::FORMAT_RGBA8, data); + p_image->create(width, height, 0, Image::FORMAT_RGBA8, data); - } else { // data is in indexed format, extend it + } else { // data is in indexed format, extend it - // Palette data - PoolVector<uint8_t> palette_data; - palette_data.resize(color_table_size * 4); + // Palette data + PoolVector<uint8_t> palette_data; + palette_data.resize(color_table_size * 4); - PoolVector<uint8_t>::Write palette_data_w = palette_data.write(); - uint8_t *pal = palette_data_w.ptr(); + PoolVector<uint8_t>::Write palette_data_w = palette_data.write(); + uint8_t *pal = palette_data_w.ptr(); - const uint8_t *cb = p_color_buffer; + const uint8_t *cb = p_color_buffer; - for (unsigned int i = 0; i < color_table_size; ++i) { - uint32_t color = *((uint32_t *)cb); + for (unsigned int i = 0; i < color_table_size; ++i) { + uint32_t color = *((uint32_t *)cb); - pal[i * 4 + 0] = (color >> 16) & 0xff; - pal[i * 4 + 1] = (color >> 8) & 0xff; - pal[i * 4 + 2] = (color)&0xff; - pal[i * 4 + 3] = 0xff; + pal[i * 4 + 0] = (color >> 16) & 0xff; + pal[i * 4 + 1] = (color >> 8) & 0xff; + pal[i * 4 + 2] = (color)&0xff; + pal[i * 4 + 3] = 0xff; - cb += 4; - } - // Extend palette to image - PoolVector<uint8_t> extended_data; - extended_data.resize(data.size() * 4); + cb += 4; + } + // Extend palette to image + PoolVector<uint8_t> extended_data; + extended_data.resize(data.size() * 4); - PoolVector<uint8_t>::Write ex_w = extended_data.write(); - uint8_t *dest = ex_w.ptr(); + PoolVector<uint8_t>::Write ex_w = extended_data.write(); + uint8_t *dest = ex_w.ptr(); - const int num_pixels = width * height; + const int num_pixels = width * height; - for (int i = 0; i < num_pixels; i++) { - dest[0] = pal[write_buffer[i] * 4 + 0]; - dest[1] = pal[write_buffer[i] * 4 + 1]; - dest[2] = pal[write_buffer[i] * 4 + 2]; - dest[3] = pal[write_buffer[i] * 4 + 3]; + for (int i = 0; i < num_pixels; i++) { + dest[0] = pal[write_buffer[i] * 4 + 0]; + dest[1] = pal[write_buffer[i] * 4 + 1]; + dest[2] = pal[write_buffer[i] * 4 + 2]; + dest[3] = pal[write_buffer[i] * 4 + 3]; - dest += 4; - } - p_image->create(width, height, 0, Image::FORMAT_RGBA8, extended_data); + dest += 4; } + p_image->create(width, height, 0, Image::FORMAT_RGBA8, extended_data); } } return err; diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index f1b3fa2ac6..5a76f32977 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -114,7 +114,7 @@ void CSGBrush::_regen_face_aabbs() { faces.write[i].aabb.position = faces[i].vertices[0]; faces.write[i].aabb.expand_to(faces[i].vertices[1]); faces.write[i].aabb.expand_to(faces[i].vertices[2]); - faces.write[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision erros + faces.write[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision errors } } diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp index e6bfa5525d..0d26943af6 100644 --- a/modules/csg/csg_gizmos.cpp +++ b/modules/csg/csg_gizmos.cpp @@ -377,7 +377,7 @@ void CSGShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { break; } - p_gizmo->add_mesh(mesh, false, RID(), solid_material); + p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), solid_material); } if (Object::cast_to<CSGSphere>(cs)) { diff --git a/modules/cvtt/SCsub b/modules/cvtt/SCsub index 142af0c800..746b23ca28 100644 --- a/modules/cvtt/SCsub +++ b/modules/cvtt/SCsub @@ -6,19 +6,18 @@ Import('env_modules') env_cvtt = env_modules.Clone() # Thirdparty source files -if env['builtin_squish']: - thirdparty_dir = "#thirdparty/cvtt/" - thirdparty_sources = [ - "ConvectionKernels.cpp" - ] +thirdparty_dir = "#thirdparty/cvtt/" +thirdparty_sources = [ + "ConvectionKernels.cpp" +] - thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env_cvtt.Prepend(CPPPATH=[thirdparty_dir]) +env_cvtt.Prepend(CPPPATH=[thirdparty_dir]) - env_thirdparty = env_cvtt.Clone() - env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) +env_thirdparty = env_cvtt.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) # Godot source files env_cvtt.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/gdnative/doc_classes/NativeScript.xml b/modules/gdnative/doc_classes/NativeScript.xml index e34e209374..dc735546e3 100644 --- a/modules/gdnative/doc_classes/NativeScript.xml +++ b/modules/gdnative/doc_classes/NativeScript.xml @@ -42,7 +42,7 @@ </description> </method> <method name="new" qualifiers="vararg"> - <return type="Object"> + <return type="Variant"> </return> <description> Constructs a new object of the base type with a script of this type already attached. diff --git a/modules/gdnative/doc_classes/PluginScript.xml b/modules/gdnative/doc_classes/PluginScript.xml index b07122bbdf..33b5f02bd4 100644 --- a/modules/gdnative/doc_classes/PluginScript.xml +++ b/modules/gdnative/doc_classes/PluginScript.xml @@ -8,7 +8,7 @@ </tutorials> <methods> <method name="new" qualifiers="vararg"> - <return type="Object"> + <return type="Variant"> </return> <description> Returns a new instance of the script. diff --git a/modules/gdnative/gdnative/array.cpp b/modules/gdnative/gdnative/array.cpp index 1ef8e9f900..e97a75cca8 100644 --- a/modules/gdnative/gdnative/array.cpp +++ b/modules/gdnative/gdnative/array.cpp @@ -327,6 +327,15 @@ godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_b return res; } +godot_array GDAPI godot_array_slice(const godot_array *p_self, const godot_int p_begin, const godot_int p_end, const godot_int p_step, const godot_bool p_deep) { + const Array *self = (const Array *)p_self; + godot_array res; + Array *val = (Array *)&res; + memnew_placement(val, Array); + *val = self->slice(p_begin, p_end, p_step, p_deep); + return res; +} + godot_variant GDAPI godot_array_max(const godot_array *p_self) { const Array *self = (const Array *)p_self; godot_variant v; diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 03258584ce..55ba4ecc1e 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -80,6 +80,17 @@ ["const godot_vector2 *", "p_self"], ["const godot_vector2 *", "p_to"] ] + }, + { + "name": "godot_array_slice", + "return_type": "godot_array", + "arguments": [ + ["const godot_array *", "p_self"], + ["const godot_int", "p_begin"], + ["const godot_int", "p_end"], + ["const godot_int", "p_step"], + ["const godot_bool", "p_deep"] + ] } ] }, diff --git a/modules/gdnative/include/gdnative/array.h b/modules/gdnative/include/gdnative/array.h index 10ef8a73d2..2e3ce58033 100644 --- a/modules/gdnative/include/gdnative/array.h +++ b/modules/gdnative/include/gdnative/array.h @@ -132,6 +132,8 @@ void GDAPI godot_array_destroy(godot_array *p_self); godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_bool p_deep); +godot_array GDAPI godot_array_slice(const godot_array *p_self, const godot_int p_begin, const godot_int p_end, const godot_int p_delta, const godot_bool p_deep); + godot_variant GDAPI godot_array_max(const godot_array *p_self); godot_variant GDAPI godot_array_min(const godot_array *p_self); diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index 7f52f5736c..8a05b6cfa3 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -64,9 +64,9 @@ typedef enum { GODOT_PROPERTY_HINT_LAYERS_3D_RENDER, GODOT_PROPERTY_HINT_LAYERS_3D_PHYSICS, GODOT_PROPERTY_HINT_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," - GODOT_PROPERTY_HINT_DIR, ///< a directort path must be passed + GODOT_PROPERTY_HINT_DIR, ///< a directory path must be passed GODOT_PROPERTY_HINT_GLOBAL_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," - GODOT_PROPERTY_HINT_GLOBAL_DIR, ///< a directort path must be passed + GODOT_PROPERTY_HINT_GLOBAL_DIR, ///< a directory path must be passed GODOT_PROPERTY_HINT_RESOURCE_TYPE, ///< a resource object type GODOT_PROPERTY_HINT_MULTILINE_TEXT, ///< used for string properties that can contain multiple lines GODOT_PROPERTY_HINT_PLACEHOLDER_TEXT, ///< used to set a placeholder text for string properties diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp index 9de073fc8e..22898a73ce 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.cpp +++ b/modules/gdnative/pluginscript/pluginscript_language.cpp @@ -200,7 +200,7 @@ void PluginScriptLanguage::get_recognized_extensions(List<String> *p_extensions) } void PluginScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { - // TODO: provid this statically in `godot_pluginscript_language_desc` ? + // TODO: provide this statically in `godot_pluginscript_language_desc` ? if (_desc.get_public_functions) { Array functions; _desc.get_public_functions(_data, (godot_array *)&functions); @@ -212,7 +212,7 @@ void PluginScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) c } void PluginScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_constants) const { - // TODO: provid this statically in `godot_pluginscript_language_desc` ? + // TODO: provide this statically in `godot_pluginscript_language_desc` ? if (_desc.get_public_constants) { Dictionary constants; _desc.get_public_constants(_data, (godot_dictionary *)&constants); diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.cpp b/modules/gdnative/videodecoder/video_stream_gdnative.cpp index be131c5402..212ff51b80 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.cpp +++ b/modules/gdnative/videodecoder/video_stream_gdnative.cpp @@ -149,7 +149,7 @@ void VideoStreamPlaybackGDNative::update(float p_delta) { if (mix_callback) { if (pcm_write_idx >= 0) { // Previous remains - int mixed = mix_callback(mix_udata, pcm, samples_decoded); + int mixed = mix_callback(mix_udata, pcm + pcm_write_idx * num_channels, samples_decoded); if (mixed == samples_decoded) { pcm_write_idx = -1; } else { diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index 6285e6bb54..74e653ce43 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -8,5 +8,12 @@ env_gdscript = env_modules.Clone() env_gdscript.add_source_files(env.modules_sources, "*.cpp") if env['tools']: - env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp") - env_gdscript.add_source_files(env.modules_sources, "./language_server/*.cpp") + env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp") + + # Those two modules are required for the language server protocol + if env['module_jsonrpc_enabled'] and env['module_websocket_enabled']: + env_gdscript.add_source_files(env.modules_sources, "./language_server/*.cpp") + else: + # Using a define in the disabled case, to avoid having an extra define + # in regular builds where all modules are enabled. + env_gdscript.Append(CPPDEFINES=['GDSCRIPT_NO_LSP']) diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index ad47323613..4efa90fd86 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -1115,7 +1115,11 @@ <argument index="1" name="step" type="float"> </argument> <description> - Snaps float value [code]s[/code] to a given [code]step[/code]. + Snaps float value [code]s[/code] to a given [code]step[/code]. This can also be used to round a floating point number to an arbitrary number of decimals. + [codeblock] + stepify(100, 32) # Returns 96 + stepify(3.14159, 0.01) # Returns 3.14 + [/codeblock] </description> </method> <method name="str" qualifiers="vararg"> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index d606a41fab..6f43361914 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -19,7 +19,7 @@ </description> </method> <method name="new" qualifiers="vararg"> - <return type="Object"> + <return type="Variant"> </return> <description> Returns a new instance of the script. diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 5dab063061..c8ec80c101 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -99,7 +99,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco #endif instance->owner->set_script_instance(instance); - /* STEP 2, INITIALIZE AND CONSRTUCT */ + /* STEP 2, INITIALIZE AND CONSTRUCT */ #ifndef NO_THREADS GDScriptLanguage::singleton->lock->lock(); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 7166189416..dea2225e91 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1520,8 +1520,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo if (ret2 < 0) return ERR_PARSE_ERROR; + int message_ret = 0; + if (as->message) { + message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false); + if (message_ret < 0) + return ERR_PARSE_ERROR; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT); codegen.opcodes.push_back(ret2); + codegen.opcodes.push_back(message_ret); #endif } break; case GDScriptParser::Node::TYPE_BREAKPOINT: { @@ -2064,7 +2072,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa } instance->owner->set_script_instance(instance); - /* STEP 2, INITIALIZE AND CONSRTUCT */ + /* STEP 2, INITIALIZE AND CONSTRUCT */ Variant::CallError ce; p_script->initializer->call(instance, NULL, 0, ce); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 7c01e85ff7..9b3bf8ad5b 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -223,7 +223,7 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false); + ScriptDebugger::get_singleton()->debug(this, false, true); return true; } else { return false; @@ -237,7 +237,8 @@ bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) _debug_parse_err_line = -1; _debug_parse_err_file = ""; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, p_allow_continue); + bool is_error_breakpoint = p_error != "Breakpoint"; + ScriptDebugger::get_singleton()->debug(this, p_allow_continue, is_error_breakpoint); return true; } else { return false; @@ -1936,9 +1937,18 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context Ref<GDScript> script = base_type.script_type; if (script.is_valid()) { if (!_static && !p_only_functions) { - for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); + if (p_context.base && p_context.base->get_script_instance()) { + List<PropertyInfo> members; + p_context.base->get_script_instance()->get_property_list(&members); + for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); + } + } else { + for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); + } } } if (!p_only_functions) { @@ -2826,6 +2836,16 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS); options.insert(option.display, option); } + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); + } } List<StringName> native_classes; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 68f2a9473e..bdeea9cef3 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1475,20 +1475,25 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSERT) { - CHECK_SPACE(2); + CHECK_SPACE(3); #ifdef DEBUG_ENABLED GET_VARIANT_PTR(test, 1); + GET_VARIANT_PTR(message, 2); bool result = test->booleanize(); if (!result) { - - err_text = "Assertion failed."; + const String &message_str = *message; + if (message_str.empty()) { + err_text = "Assertion failed."; + } else { + err_text = "Assertion failed: " + message_str; + } OPCODE_BREAK; } #endif - ip += 2; + ip += 3; } DISPATCH_OPCODE; diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index ad8bf5b2c0..97790e00bb 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -106,6 +106,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) { "typeof", "type_exists", "char", + "ord", "str", "print", "printt", @@ -665,6 +666,33 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ CharType result[2] = { *p_args[0], 0 }; r_ret = String(result); } break; + case TEXT_ORD: { + + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type() != Variant::STRING) { + + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + r_ret = Variant(); + return; + } + + String str = p_args[0]->operator String(); + + if (str.length() != 1) { + + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + r_ret = RTR("Expected a string of length 1 (a character)."); + return; + } + + r_ret = str.get(0); + + } break; case TEXT_STR: { if (p_arg_count < 1) { r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; @@ -1507,6 +1535,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) { case TYPE_OF: case TYPE_EXISTS: case TEXT_CHAR: + case TEXT_ORD: case TEXT_STR: case COLOR8: case LEN: @@ -1849,6 +1878,13 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; + case TEXT_ORD: { + + MethodInfo mi("ord", PropertyInfo(Variant::STRING, "char")); + mi.return_val.type = Variant::INT; + return mi; + + } break; case TEXT_STR: { MethodInfo mi("str"); diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h index 8f7ba76d2c..9ea5dd46cf 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_functions.h @@ -97,6 +97,7 @@ public: TYPE_OF, TYPE_EXISTS, TEXT_CHAR, + TEXT_ORD, TEXT_STR, TEXT_PRINT, TEXT_PRINT_TABBED, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a9f22225a0..967b0c83ae 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -252,6 +252,16 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } } + // Check that the next token is not TK_CURSOR and if it is, the offset should be incremented. + int next_valid_offset = 1; + if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) { + next_valid_offset++; + // There is a chunk of the identifier that also needs to be ignored (not always there!) + if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) { + next_valid_offset++; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { //subexpression () tokenizer->advance(); @@ -668,7 +678,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s expr = cn; } - } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) { + } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name //function or constructor @@ -1197,7 +1207,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (_get_completable_identifier(COMPLETION_INDEX, identifier)) { if (identifier == StringName()) { - identifier = "@temp"; //so it parses allright + identifier = "@temp"; //so it parses alright } completion_node = op; @@ -1389,9 +1399,6 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s unary = true; break; case OperatorNode::OP_NEG: - priority = 1; - unary = true; - break; case OperatorNode::OP_POS: priority = 1; unary = true; @@ -2836,15 +2843,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { assigned = subexpr; } else { - ConstantNode *c = alloc_node<ConstantNode>(); - if (lv->datatype.has_type && lv->datatype.kind == DataType::BUILTIN) { - Variant::CallError err; - c->value = Variant::construct(lv->datatype.builtin_type, NULL, 0, err); - } else { - c->value = Variant(); - } - c->line = var_line; - assigned = c; + assigned = _get_default_value_for_type(lv->datatype, var_line); } lv->assign = assigned; //must be added later, to avoid self-referencing. @@ -3270,15 +3269,36 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { case GDScriptTokenizer::TK_PR_ASSERT: { tokenizer->advance(); - Node *condition = _parse_and_reduce_expression(p_block, p_static); - if (!condition) { - if (_recover_from_completion()) { - break; - } + + if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { + _set_error("Expected '(' after assert"); return; } + + tokenizer->advance(); + + Vector<Node *> args; + const bool result = _parse_arguments(p_block, args, p_static); + if (!result) { + return; + } + + if (args.empty() || args.size() > 2) { + _set_error("Wrong number of arguments, expected 1 or 2"); + return; + } + AssertNode *an = alloc_node<AssertNode>(); - an->condition = condition; + an->condition = _reduce_expression(args[0], p_static); + + if (args.size() == 2) { + an->message = _reduce_expression(args[1], p_static); + } else { + ConstantNode *message_node = alloc_node<ConstantNode>(); + message_node->value = String(); + an->message = message_node; + } + p_block->statements.push_back(an); if (!_end_statement()) { @@ -3366,7 +3386,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) { return; } - if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty() || p_class->classname_used) { + if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty()) { _set_error("\"extends\" must be used before anything else."); return; @@ -3523,6 +3543,15 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } + if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) { + const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name); + if (autoload_path.begins_with("*")) { + // It's a singleton, and not just a regular AutoLoad script. + _set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error."); + } + return; + } + tokenizer->advance(2); if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { @@ -4829,35 +4858,29 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - Variant::Type initial_type = member.data_type.has_type ? member.data_type.builtin_type : member._export.type; - - if (initial_type != Variant::NIL && initial_type != Variant::OBJECT) { - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = member.identifier; + Node *expr; - Node *expr; + if (member.data_type.has_type) { + expr = _get_default_value_for_type(member.data_type); + } else { + DataType exported_type; + exported_type.has_type = true; + exported_type.kind = DataType::BUILTIN; + exported_type.builtin_type = member._export.type; + expr = _get_default_value_for_type(exported_type); + } - // Make sure arrays and dictionaries are not shared - if (initial_type == Variant::ARRAY) { - expr = alloc_node<ArrayNode>(); - } else if (initial_type == Variant::DICTIONARY) { - expr = alloc_node<DictionaryNode>(); - } else { - ConstantNode *cn = alloc_node<ConstantNode>(); - Variant::CallError ce2; - cn->value = Variant::construct(initial_type, NULL, 0, ce2); - expr = cn; - } + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->name = member.identifier; - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_INIT_ASSIGN; - op->arguments.push_back(id); - op->arguments.push_back(expr); + OperatorNode *op = alloc_node<OperatorNode>(); + op->op = OperatorNode::OP_INIT_ASSIGN; + op->arguments.push_back(id); + op->arguments.push_back(expr); - p_class->initializer->statements.push_back(op); + p_class->initializer->statements.push_back(op); - member.initial_assignment = op; - } + member.initial_assignment = op; } if (autoexport && member.data_type.has_type) { @@ -5245,6 +5268,31 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive return; } p = NULL; + } else { + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + if (name == base) { + String singleton_path = ProjectSettings::get_singleton()->get(s); + if (singleton_path.begins_with("*")) { + singleton_path = singleton_path.right(1); + } + if (!singleton_path.begins_with("res://")) { + singleton_path = "res://" + singleton_path; + } + base_script = ResourceLoader::load(singleton_path); + if (!base_script.is_valid()) { + _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line); + return; + } + p = NULL; + } + } } while (p) { @@ -5589,9 +5637,49 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, } name_part++; continue; - } else { - p = current_class; } + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + String singleton_path; + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + if (name == id) { + singleton_path = ProjectSettings::get_singleton()->get(s); + if (singleton_path.begins_with("*")) { + singleton_path = singleton_path.right(1); + } + if (!singleton_path.begins_with("res://")) { + singleton_path = "res://" + singleton_path; + } + break; + } + } + if (!singleton_path.empty()) { + Ref<Script> script = ResourceLoader::load(singleton_path); + Ref<GDScript> gds = script; + if (gds.is_valid()) { + if (!gds->is_valid()) { + _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line); + return DataType(); + } + result.kind = DataType::GDSCRIPT; + result.script_type = gds; + } else if (script.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = script; + } else { + _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line); + return DataType(); + } + name_part++; + continue; + } + + p = current_class; } else if (base_type.kind == DataType::CLASS) { p = base_type.class_type; } @@ -5652,28 +5740,35 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, } } - // Still look for class constants in parent script + // Still look for class constants in parent scripts if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) { Ref<Script> scr = base_type.script_type; ERR_FAIL_COND_V(scr.is_null(), result); - Map<StringName, Variant> constants; - scr->get_constants(&constants); + while (scr.is_valid()) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); - if (constants.has(id)) { - Ref<GDScript> gds = constants[id]; + if (constants.has(id)) { + Ref<GDScript> gds = constants[id]; - if (gds.is_valid()) { - result.kind = DataType::GDSCRIPT; - result.script_type = gds; - found = true; - } else { - Ref<Script> scr2 = constants[id]; - if (scr2.is_valid()) { - result.kind = DataType::SCRIPT; - result.script_type = scr2; + if (gds.is_valid()) { + result.kind = DataType::GDSCRIPT; + result.script_type = gds; found = true; + } else { + Ref<Script> scr2 = constants[id]; + if (scr2.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = scr2; + found = true; + } } } + if (found) { + break; + } else { + scr = scr->get_base_script(); + } } } @@ -6065,6 +6160,31 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data return false; } +GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) { + Node *result; + + if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) { + if (p_type.builtin_type == Variant::ARRAY) { + result = alloc_node<ArrayNode>(); + } else if (p_type.builtin_type == Variant::DICTIONARY) { + result = alloc_node<DictionaryNode>(); + } else { + ConstantNode *c = alloc_node<ConstantNode>(); + Variant::CallError err; + c->value = Variant::construct(p_type.builtin_type, NULL, 0, err); + result = c; + } + } else { + ConstantNode *c = alloc_node<ConstantNode>(); + c->value = Variant(); + result = c; + } + + result->line = p_line; + + return result; +} + GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { #ifdef DEBUG_ENABLED if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) { @@ -6510,7 +6630,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { return DataType(); } } - if (check_types && !node_type.has_type) { + if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) { // Can infer indexing type for some variant types DataType result; result.has_type = true; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 72aa819a8c..04ce9cf4c6 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -481,7 +481,12 @@ public: struct AssertNode : public Node { Node *condition; - AssertNode() { type = TYPE_ASSERT; } + Node *message; + AssertNode() : + condition(0), + message(0) { + type = TYPE_ASSERT; + } }; struct BreakpointNode : public Node { @@ -610,6 +615,7 @@ private: bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const; bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const; bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const; + Node *_get_default_value_for_type(const DataType &p_type, int p_line = -1); DataType _reduce_node_type(Node *p_node); DataType _reduce_function_call_type(const OperatorNode *p_call); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 45f9ec9c6a..9e8727ec58 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -189,6 +189,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p lsp::DocumentSymbol symbol; const GDScriptParser::ClassNode::Constant &c = E->value(); const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression); + ERR_FAIL_COND(!node); symbol.name = E->key(); symbol.kind = lsp::SymbolKind::Constant; symbol.deprecated = false; @@ -674,6 +675,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode const GDScriptParser::ClassNode::Constant &c = E->value(); const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression); + ERR_FAIL_COND_V(!node, class_api); Dictionary api; api["name"] = E->key(); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index afe461b68e..ce3de9bc3b 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -100,9 +100,10 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { String root_uri = p_params["rootUri"]; String root = p_params["rootPath"]; - bool is_same_workspace = root == workspace->root; + bool is_same_workspace; +#ifndef WINDOWS_ENABLED is_same_workspace = root.to_lower() == workspace->root.to_lower(); -#ifdef WINDOWS_ENABLED +#else is_same_workspace = root.replace("\\", "/").to_lower() == workspace->root.to_lower(); #endif @@ -142,6 +143,7 @@ void GDScriptLanguageProtocol::poll() { Error GDScriptLanguageProtocol::start(int p_port) { if (server == NULL) { server = dynamic_cast<WebSocketServer *>(ClassDB::instance("WebSocketServer")); + ERR_FAIL_COND_V(!server, FAILED); server->set_buffers(8192, 1024, 8192, 1024); // 8mb should be way more than enough server->connect("data_received", this, "on_data_received"); server->connect("client_connected", this, "on_client_connected"); diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 9bea4557ac..62c212a2bd 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -64,7 +64,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { void GDScriptLanguageServer::start() { int port = (int)_EDITOR_GET("network/language_server/remote_port"); if (protocol.start(port) == OK) { - EditorNode::get_log()->add_message("** GDScript Language Server Started **"); + EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); ERR_FAIL_COND(thread != NULL || thread_exit); thread_exit = false; thread = Thread::create(GDScriptLanguageServer::thread_main, this); @@ -78,7 +78,7 @@ void GDScriptLanguageServer::stop() { memdelete(thread); thread = NULL; protocol.stop(); - EditorNode::get_log()->add_message("** GDScript Language Server Stopped **"); + EditorNode::get_log()->add_message("--- GDScript language server stopped ---", EditorLog::MSG_TYPE_EDITOR); } void register_lsp_types() { diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index f211fae526..7c58c7aa15 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -380,8 +380,8 @@ GDScriptTextDocument::~GDScriptTextDocument() { memdelete(file_checker); } -void GDScriptTextDocument::sync_script_content(const String &p_uri, const String &p_content) { - String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_uri); +void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { + String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); } diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index d07949b34b..94b9e8c2d9 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -34,10 +34,8 @@ #include "core/io/resource_loader.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" -#include "editor/gdscript_highlighter.h" #include "gdscript.h" #include "gdscript_tokenizer.h" -#include "language_server/gdscript_language_server.h" GDScriptLanguage *script_language_gd = NULL; Ref<ResourceFormatLoaderGDScript> resource_loader_gd; @@ -45,10 +43,15 @@ Ref<ResourceFormatSaverGDScript> resource_saver_gd; #ifdef TOOLS_ENABLED -#include "core/engine.h" #include "editor/editor_export.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/gdscript_highlighter.h" + +#ifndef GDSCRIPT_NO_LSP +#include "core/engine.h" +#include "language_server/gdscript_language_server.h" +#endif // !GDSCRIPT_NO_LSP class EditorExportGDScript : public EditorExportPlugin { @@ -137,13 +140,15 @@ static void _editor_init() { gd_export.instance(); EditorExport::get_singleton()->add_export_plugin(gd_export); +#ifndef GDSCRIPT_NO_LSP register_lsp_types(); GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer); EditorNode::get_singleton()->add_editor_plugin(lsp_plugin); Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton())); +#endif // !GDSCRIPT_NO_LSP } -#endif +#endif // TOOLS_ENABLED void register_gdscript_types() { @@ -162,7 +167,7 @@ void register_gdscript_types() { #ifdef TOOLS_ENABLED ScriptEditor::register_create_syntax_highlighter_function(GDScriptSyntaxHighlighter::create); EditorNode::add_init_callback(_editor_init); -#endif +#endif // TOOLS_ENABLED } void unregister_gdscript_types() { diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 07b4f7f596..7e2986ca85 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -884,9 +884,15 @@ void GridMapEditor::update_palette() { if (mesh_library.is_null()) { last_mesh_library = NULL; + search_box->set_text(""); + search_box->set_editable(false); + info_message->show(); return; } + search_box->set_editable(true); + info_message->hide(); + Vector<int> ids; ids = mesh_library->get_item_list(); @@ -1069,6 +1075,7 @@ void GridMapEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", this, "_node_removed"); mesh_library_palette->connect("item_selected", this, "_item_selected_cbk"); for (int i = 0; i < 3; i++) { @@ -1085,6 +1092,7 @@ void GridMapEditor::_notification(int p_what) { } break; case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", this, "_node_removed"); _clear_clipboard_data(); for (int i = 0; i < 3; i++) { @@ -1198,6 +1206,7 @@ void GridMapEditor::_bind_methods() { ClassDB::bind_method("_floor_changed", &GridMapEditor::_floor_changed); ClassDB::bind_method("_floor_mouse_exited", &GridMapEditor::_floor_mouse_exited); ClassDB::bind_method("_set_selection", &GridMapEditor::_set_selection); + ClassDB::bind_method("_node_removed", &GridMapEditor::_node_removed); ClassDB::bind_method(D_METHOD("_set_display_mode", "mode"), &GridMapEditor::_set_display_mode); } @@ -1296,6 +1305,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { search_box = memnew(LineEdit); search_box->set_h_size_flags(SIZE_EXPAND_FILL); + search_box->set_placeholder(TTR("Filter meshes")); hb->add_child(search_box); search_box->connect("text_changed", this, "_text_changed"); search_box->connect("gui_input", this, "_sbox_input"); @@ -1331,6 +1341,14 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { add_child(mesh_library_palette); mesh_library_palette->set_v_size_flags(SIZE_EXPAND_FILL); + info_message = memnew(Label); + info_message->set_text(TTR("Give a MeshLibrary resource to this GridMap to use its meshes.")); + info_message->set_valign(Label::VALIGN_CENTER); + info_message->set_align(Label::ALIGN_CENTER); + info_message->set_autowrap(true); + info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); + mesh_library_palette->add_child(info_message); + edit_axis = Vector3::AXIS_Y; edit_floor[0] = -1; edit_floor[1] = -1; @@ -1346,7 +1364,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { paste_mesh = VisualServer::get_singleton()->mesh_create(); { - //selection mesh create + // Selection mesh create. PoolVector<Vector3> lines; PoolVector<Vector3> triangles; @@ -1424,7 +1442,6 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { inner_mat.instance(); inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2)); - //inner_mat->set_flag(SpatialMaterial::FLAG_ONTOP, true); inner_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); inner_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); @@ -1444,7 +1461,6 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { selection_floor_mat->set_on_top_of_alpha(); selection_floor_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); selection_floor_mat->set_line_width(3.0); - //selection_floor_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); d[VS::ARRAY_VERTEX] = lines; VisualServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, VS::PRIMITIVE_LINES, d); diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index d174ac1035..103913485f 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -197,12 +197,16 @@ class GridMapEditor : public VBoxContainer { RID instance; }; + ItemList *mesh_library_palette; + Label *info_message; + + EditorNode *editor; + void update_grid(); void _configure(); void _menu_option(int); void update_palette(); void _set_display_mode(int p_mode); - ItemList *mesh_library_palette; void _item_selected_cbk(int idx); void _update_cursor_transform(); void _update_cursor_instance(); @@ -227,7 +231,6 @@ class GridMapEditor : public VBoxContainer { void _delete_selection(); void _fill_selection(); - EditorNode *editor; bool do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click); friend class GridMapEditorPlugin; diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp index b18b48d1b0..e1bba60f2f 100644 --- a/modules/jsonrpc/jsonrpc.cpp +++ b/modules/jsonrpc/jsonrpc.cpp @@ -47,11 +47,11 @@ void JSONRPC::_bind_methods() { ClassDB::bind_method(D_METHOD("make_notification", "method", "params"), &JSONRPC::make_notification); ClassDB::bind_method(D_METHOD("make_response_error", "code", "message", "id"), &JSONRPC::make_response_error, DEFVAL(Variant())); - BIND_ENUM_CONSTANT(ParseError) - BIND_ENUM_CONSTANT(InvalidRequest) - BIND_ENUM_CONSTANT(MethodNotFound) - BIND_ENUM_CONSTANT(InvalidParams) - BIND_ENUM_CONSTANT(InternalError) + BIND_ENUM_CONSTANT(PARSE_ERROR) + BIND_ENUM_CONSTANT(INVALID_REQUEST) + BIND_ENUM_CONSTANT(METHOD_NOT_FOUND) + BIND_ENUM_CONSTANT(INVALID_PARAMS) + BIND_ENUM_CONSTANT(INTERNAL_ERROR) } Dictionary JSONRPC::make_response_error(int p_code, const String &p_message, const Variant &p_id) const { @@ -120,7 +120,7 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem } if (object == NULL || !object->has_method(method)) { - ret = make_response_error(JSONRPC::MethodNotFound, "Method not found", id); + ret = make_response_error(JSONRPC::METHOD_NOT_FOUND, "Method not found", id); } else { Variant call_ret = object->callv(method, args); if (id.get_type() != Variant::NIL) { @@ -138,10 +138,10 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem } ret = arr_ret; } else { - ret = make_response_error(JSONRPC::InvalidRequest, "Invalid Request"); + ret = make_response_error(JSONRPC::INVALID_REQUEST, "Invalid Request"); } } else { - ret = make_response_error(JSONRPC::InvalidRequest, "Invalid Request"); + ret = make_response_error(JSONRPC::INVALID_REQUEST, "Invalid Request"); } return ret; } @@ -155,7 +155,7 @@ String JSONRPC::process_string(const String &p_input) { String err_message; int err_line; if (OK != JSON::parse(p_input, input, err_message, err_line)) { - ret = make_response_error(JSONRPC::ParseError, "Parse error"); + ret = make_response_error(JSONRPC::PARSE_ERROR, "Parse error"); } else { ret = process_action(input, true); } diff --git a/modules/jsonrpc/jsonrpc.h b/modules/jsonrpc/jsonrpc.h index bcb34ecc65..91897d0b55 100644 --- a/modules/jsonrpc/jsonrpc.h +++ b/modules/jsonrpc/jsonrpc.h @@ -47,11 +47,11 @@ public: ~JSONRPC(); enum ErrorCode { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, + PARSE_ERROR = -32700, + INVALID_REQUEST = -32600, + METHOD_NOT_FOUND = -32601, + INVALID_PARAMS = -32602, + INTERNAL_ERROR = -32603, }; Dictionary make_response_error(int p_code, const String &p_message, const Variant &p_id = Variant()) const; diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index f751719531..4c1ebd8d74 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -18,7 +18,7 @@ android_arch_dirs = { def get_android_out_dir(env): - return os.path.join(Dir('#platform/android/java/libs').abspath, + return os.path.join(Dir('#platform/android/java/lib/libs').abspath, 'release' if env['target'] == 'release' else 'debug', android_arch_dirs[env['android_arch']]) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 4c9dd9c1a9..e14e919f92 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1101,7 +1101,7 @@ bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const S _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false); + ScriptDebugger::get_singleton()->debug(this, false, true); return true; } else { return false; diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index c745fe321b..ab3a5d1aea 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -37,9 +37,8 @@ entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that. We make SCons restore the NuGet packages in the project directory instead in this case. --> - <HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> - <HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> - <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? --> + <HintPath Condition=" '$(SolutionDir)' != '' And Exists('$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll') ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath Condition=" '$(SolutionDir)' == '' Or !Exists('$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll') ">$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs index aefc51545e..4f93ef8530 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs @@ -16,7 +16,7 @@ namespace GodotTools { private void AddFile(string srcPath, string dstPath, bool remap = false) { - AddFile(dstPath, File.ReadAllBytes(srcPath), remap); + AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap); } public override void _ExportFile(string path, string type, string[] features) diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 1888bb3cb9..74710db224 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1785,6 +1785,9 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { output.append("uint32_t get_bindings_version() { return "); output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); + output.append("uint32_t get_cs_glue_version() { return "); + output.append(String::num_uint64(CS_GLUE_VERSION) + "; }\n"); + output.append("\nvoid register_generated_icalls() " OPEN_BLOCK); output.append("\tgodot_register_glue_header_icalls();\n"); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index cd1ca2a2c7..5a84d9e3b8 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -429,6 +429,7 @@ void register_editor_internal_calls() { mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit); mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); + mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot); mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay); mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop); mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs index 15adf0a13b..ce34cd6a99 100644 --- a/modules/mono/glue/Managed/Files/Mathf.cs +++ b/modules/mono/glue/Managed/Files/Mathf.cs @@ -158,6 +158,11 @@ namespace Godot public static bool IsEqualApprox(real_t a, real_t b) { + // Check for exact equality first, required to handle "infinity" values. + if (a == b) { + return true; + } + // Then check for approximate equality. real_t tolerance = Epsilon * Abs(a); if (tolerance < Epsilon) { tolerance = Epsilon; diff --git a/modules/mono/glue/Managed/Files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs index b96f01bc2e..6cffc7f01d 100644 --- a/modules/mono/glue/Managed/Files/MathfEx.cs +++ b/modules/mono/glue/Managed/Files/MathfEx.cs @@ -48,7 +48,12 @@ namespace Godot public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance) { + // Check for exact equality first, required to handle "infinity" values. + if (a == b) { + return true; + } + // Then check for approximate equality. return Abs(a - b) < tolerance; } } -}
\ No newline at end of file +} diff --git a/modules/mono/glue/Managed/Files/Rect2.cs b/modules/mono/glue/Managed/Files/Rect2.cs index f3dc9d8490..99542d0c0a 100644 --- a/modules/mono/glue/Managed/Files/Rect2.cs +++ b/modules/mono/glue/Managed/Files/Rect2.cs @@ -157,13 +157,13 @@ namespace Godot public bool Intersects(Rect2 b) { - if (_position.x > b._position.x + b._size.x) + if (_position.x >= b._position.x + b._size.x) return false; - if (_position.x + _size.x < b._position.x) + if (_position.x + _size.x <= b._position.x) return false; - if (_position.y > b._position.y + b._size.y) + if (_position.y >= b._position.y + b._size.y) return false; - if (_position.y + _size.y < b._position.y) + if (_position.y + _size.y <= b._position.y) return false; return true; diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index aa69803a58..915a01af7e 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -44,7 +44,6 @@ #include "core/project_settings.h" #include "../csharp_script.h" -#include "../glue/cs_glue_version.gen.h" #include "../godotsharp_dirs.h" #include "../utils/path_utils.h" #include "gd_mono_class.h" @@ -317,6 +316,13 @@ void GDMono::initialize() { return; #endif +#if !defined(WINDOWS_ENABLED) && !defined(NO_MONO_THREADS_SUSPEND_WORKAROUND) + // FIXME: Temporary workaround. See: https://github.com/godotengine/godot/issues/29812 + if (!OS::get_singleton()->has_environment("MONO_THREADS_SUSPEND")) { + OS::get_singleton()->set_environment("MONO_THREADS_SUSPEND", "preemptive"); + } +#endif + root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime."); @@ -388,23 +394,24 @@ uint64_t get_core_api_hash(); uint64_t get_editor_api_hash(); #endif uint32_t get_bindings_version(); +uint32_t get_cs_glue_version(); void register_generated_icalls(); #else uint64_t get_core_api_hash() { - CRASH_NOW(); GD_UNREACHABLE(); } #ifdef TOOLS_ENABLED uint64_t get_editor_api_hash() { - CRASH_NOW(); GD_UNREACHABLE(); } #endif uint32_t get_bindings_version() { - CRASH_NOW(); + GD_UNREACHABLE(); +} +uint32_t get_cs_glue_version() { GD_UNREACHABLE(); } @@ -680,7 +687,7 @@ bool GDMono::_load_core_api_assembly() { APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE); core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash || GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - CS_GLUE_VERSION != api_assembly_ver.cs_glue_version; + GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; if (!core_api_assembly_out_of_sync) { GDMonoUtils::update_godot_api_cache(); @@ -715,7 +722,7 @@ bool GDMono::_load_editor_api_assembly() { APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR); editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash || GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - CS_GLUE_VERSION != api_assembly_ver.cs_glue_version; + GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; } else { editor_api_assembly_out_of_sync = false; } diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index 5a0d728953..6d91075ce3 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -159,7 +159,7 @@ void GDMonoLog::initialize() { log_file = FileAccess::open(log_file_path, FileAccess::WRITE); if (!log_file) { - ERR_PRINT("Mono: Cannot create log file."); + ERR_PRINTS("Mono: Cannot create log file at: " + log_file_path); } } diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index ae5a2cde81..716c712ccc 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -208,14 +208,18 @@ String str_format(const char *p_format, ...) { #endif #if defined(MINGW_ENABLED) || defined(_MSC_VER) && _MSC_VER < 1900 -#define vsnprintf(m_buffer, m_count, m_format, m_argptr) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_argptr) +#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_args_copy) +#define gd_vscprintf(m_format, m_args_copy) _vscprintf(m_format, m_args_copy) +#else +#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf(m_buffer, m_count, m_format, m_args_copy) +#define gd_vscprintf(m_format, m_args_copy) vsnprintf(NULL, 0, p_format, m_args_copy) #endif String str_format(const char *p_format, va_list p_list) { va_list list; va_copy(list, p_list); - int len = vsnprintf(NULL, 0, p_format, list); + int len = gd_vscprintf(p_format, list); va_end(list); len += 1; // for the trailing '/0' @@ -223,7 +227,7 @@ String str_format(const char *p_format, va_list p_list) { char *buffer(memnew_arr(char, len)); va_copy(list, p_list); - vsnprintf(buffer, len, p_format, list); + gd_vsnprintf(buffer, len, p_format, list); va_end(list); String res(buffer); diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 3f8b2b1831..6bed1742eb 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -578,6 +578,10 @@ void VisualScript::get_data_connection_list(const StringName &p_func, List<DataC } } +void VisualScript::set_tool_enabled(bool p_enabled) { + is_tool_script = p_enabled; +} + void VisualScript::add_variable(const StringName &p_name, const Variant &p_default_value, bool p_export) { ERR_FAIL_COND(instances.size()); @@ -894,7 +898,7 @@ ScriptInstance *VisualScript::instance_create(Object *p_this) { #ifdef TOOLS_ENABLED - if (!ScriptServer::is_scripting_enabled()) { + if (!ScriptServer::is_scripting_enabled() && !is_tool_script) { PlaceHolderScriptInstance *sins = memnew(PlaceHolderScriptInstance(VisualScriptLanguage::singleton, Ref<Script>((Script *)this), p_this)); placeholders.insert(sins); @@ -958,7 +962,7 @@ Error VisualScript::reload(bool p_keep_state) { bool VisualScript::is_tool() const { - return false; + return is_tool_script; } bool VisualScript::is_valid() const { @@ -1164,6 +1168,11 @@ void VisualScript::_set_data(const Dictionary &p_data) { data_connect(name, data_connections[j + 0], data_connections[j + 1], data_connections[j + 2], data_connections[j + 3]); } } + + if (d.has("is_tool_script")) + is_tool_script = d["is_tool_script"]; + else + is_tool_script = false; } Dictionary VisualScript::_get_data() const { @@ -1246,6 +1255,8 @@ Dictionary VisualScript::_get_data() const { d["functions"] = funcs; + d["is_tool_script"] = is_tool_script; + return d; } @@ -2450,7 +2461,7 @@ bool VisualScriptLanguage::debug_break_parse(const String &p_file, int p_node, c _debug_parse_err_node = p_node; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false); + ScriptDebugger::get_singleton()->debug(this, false, true); return true; } else { return false; @@ -2464,7 +2475,7 @@ bool VisualScriptLanguage::debug_break(const String &p_error, bool p_allow_conti _debug_parse_err_node = -1; _debug_parse_err_file = ""; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, p_allow_continue); + ScriptDebugger::get_singleton()->debug(this, p_allow_continue, true); return true; } else { return false; diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index 098c28370d..14927c4363 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -247,6 +247,8 @@ private: Map<Object *, VisualScriptInstance *> instances; + bool is_tool_script; + #ifdef TOOLS_ENABLED Set<PlaceHolderScriptInstance *> placeholders; //void _update_placeholder(PlaceHolderScriptInstance *p_placeholder); @@ -273,6 +275,7 @@ public: Vector2 get_function_scroll(const StringName &p_name) const; void get_function_list(List<StringName> *r_functions) const; int get_function_node_id(const StringName &p_name) const; + void set_tool_enabled(bool p_enabled); void add_node(const StringName &p_func, int p_id, const Ref<VisualScriptNode> &p_node, const Point2 &p_pos = Point2()); void remove_node(const StringName &p_func, int p_id); diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index 8088a71198..3fdceacebb 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -106,6 +106,7 @@ const char *VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX "smoothstep", "posmod", "lerp_angle", + "ord", }; VisualScriptBuiltinFunc::BuiltinFunc VisualScriptBuiltinFunc::find_function(const String &p_string) { @@ -181,6 +182,7 @@ int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) { case OBJ_WEAKREF: case TYPE_OF: case TEXT_CHAR: + case TEXT_ORD: case TEXT_STR: case TEXT_PRINT: case TEXT_PRINTERR: @@ -438,6 +440,9 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const case TYPE_EXISTS: { return PropertyInfo(Variant::STRING, "type"); } break; + case TEXT_ORD: { + return PropertyInfo(Variant::STRING, "character"); + } break; case TEXT_CHAR: { return PropertyInfo(Variant::INT, "ascii"); } break; @@ -594,6 +599,7 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons case TYPE_CONVERT: { } break; + case TEXT_ORD: case TYPE_OF: { t = Variant::INT; @@ -1132,6 +1138,30 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in *r_return = String(result); } break; + case VisualScriptBuiltinFunc::TEXT_ORD: { + + if (p_inputs[0]->get_type() != Variant::STRING) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + + return; + } + + String str = p_inputs[0]->operator String(); + + if (str.length() != 1) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + *r_return = "Expected a string of length 1 (a character)."; + + return; + } + + *r_return = str.get(0); + + } break; case VisualScriptBuiltinFunc::TEXT_STR: { String str = *p_inputs[0]; @@ -1374,6 +1404,7 @@ void VisualScriptBuiltinFunc::_bind_methods() { BIND_ENUM_CONSTANT(MATH_SMOOTHSTEP); BIND_ENUM_CONSTANT(MATH_POSMOD); BIND_ENUM_CONSTANT(MATH_LERP_ANGLE); + BIND_ENUM_CONSTANT(TEXT_ORD); BIND_ENUM_CONSTANT(FUNC_MAX); } @@ -1460,6 +1491,7 @@ void register_visual_script_builtin_func_node() { VisualScriptLanguage::singleton->add_register_func("functions/built_in/typeof", create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_OF>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/type_exists", create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_EXISTS>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/char", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_CHAR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/ord", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_ORD>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/str", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_STR>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/print", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINT>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/printerr", create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINTERR>); diff --git a/modules/visual_script/visual_script_builtin_funcs.h b/modules/visual_script/visual_script_builtin_funcs.h index cf475d675d..998a6cbc6a 100644 --- a/modules/visual_script/visual_script_builtin_funcs.h +++ b/modules/visual_script/visual_script_builtin_funcs.h @@ -106,6 +106,7 @@ public: MATH_SMOOTHSTEP, MATH_POSMOD, MATH_LERP_ANGLE, + TEXT_ORD, FUNC_MAX }; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index eef3f0f8ae..7262dde359 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -591,6 +591,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { gnode->add_color_override("title_color", c); c.a = 0.7; gnode->add_color_override("close_color", c); + gnode->add_color_override("resizer_color", c); gnode->add_style_override("frame", sbf); } @@ -2226,6 +2227,10 @@ void VisualScriptEditor::_change_base_type() { select_base_type->popup_create(true, true); } +void VisualScriptEditor::_toggle_tool_script() { + script->set_tool_enabled(!script->is_tool()); +} + void VisualScriptEditor::clear_edit_menu() { memdelete(edit_menu); memdelete(left_vsplit); @@ -3447,6 +3452,7 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_update_members", &VisualScriptEditor::_update_members); ClassDB::bind_method("_change_base_type", &VisualScriptEditor::_change_base_type); ClassDB::bind_method("_change_base_type_callback", &VisualScriptEditor::_change_base_type_callback); + ClassDB::bind_method("_toggle_tool_script", &VisualScriptEditor::_toggle_tool_script); ClassDB::bind_method("_node_selected", &VisualScriptEditor::_node_selected); ClassDB::bind_method("_node_moved", &VisualScriptEditor::_node_moved); ClassDB::bind_method("_move_node", &VisualScriptEditor::_move_node); @@ -3532,6 +3538,11 @@ VisualScriptEditor::VisualScriptEditor() { left_vb->set_v_size_flags(SIZE_EXPAND_FILL); //left_vb->set_custom_minimum_size(Size2(230, 1) * EDSCALE); + CheckButton *tool_script_check = memnew(CheckButton); + tool_script_check->set_text(TTR("Make Tool:")); + left_vb->add_child(tool_script_check); + tool_script_check->connect("pressed", this, "_toggle_tool_script"); + base_type_select = memnew(Button); left_vb->add_margin_child(TTR("Base Type:"), base_type_select); base_type_select->connect("pressed", this, "_change_base_type"); diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 4f302d1d72..5df9b1a004 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -186,6 +186,7 @@ class VisualScriptEditor : public ScriptEditorBase { void _node_filter_changed(const String &p_text); void _change_base_type_callback(); void _change_base_type(); + void _toggle_tool_script(); void _member_selected(); void _member_edited(); |