diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 492fa3f965..bbdcd7fe0c 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -233,13 +233,14 @@ static String _fixstr(const String &p_what, const String &p_str) { return what; } -static void _pre_gen_shape_list(const Ref &mesh, List> &r_shape_list, bool p_convex) { +static void _pre_gen_shape_list(Ref &mesh, Vector> &r_shape_list, bool p_convex) { ERR_FAIL_NULL_MSG(mesh, "Cannot generate shape list with null mesh value"); if (!p_convex) { Ref shape = mesh->create_trimesh_shape(); r_shape_list.push_back(shape); } else { - Vector> cd = mesh->convex_decompose(); + Vector> cd; + cd.push_back(mesh->get_mesh()->create_convex_shape(true, /*Passing false, otherwise VHACD will be used to simplify (Decompose) the Mesh.*/ false)); if (cd.size()) { for (int i = 0; i < cd.size(); i++) { r_shape_list.push_back(cd[i]); @@ -248,7 +249,7 @@ static void _pre_gen_shape_list(const Ref &mesh, List, List>> &collision_map) { +Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map, Vector>> &collision_map) { // children first for (int i = 0; i < p_node->get_child_count(); i++) { Node *r = _pre_fix_node(p_node->get_child(i), p_root, collision_map); @@ -335,7 +336,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map mesh = mi->get_mesh(); if (mesh.is_valid()) { - List> shapes; + Vector> shapes; String fixed_name; if (collision_map.has(mesh)) { shapes = collision_map[mesh]; @@ -401,7 +402,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map mesh = mi->get_mesh(); if (mesh.is_valid()) { - List> shapes; + Vector> shapes; if (collision_map.has(mesh)) { shapes = collision_map[mesh]; } else { @@ -426,7 +427,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map mesh = mi->get_mesh(); if (mesh.is_valid()) { - List> shapes; + Vector> shapes; String fixed_name; if (collision_map.has(mesh)) { shapes = collision_map[mesh]; @@ -485,7 +486,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map mesh = mi->get_mesh(); if (!mesh.is_null()) { - List> shapes; + Vector> shapes; if (collision_map.has(mesh)) { shapes = collision_map[mesh]; } else if (_teststr(mesh->get_name(), "col")) { @@ -511,7 +512,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map, List>> &collision_map, Set> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps) { +Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map, Vector>> &collision_map, Set> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps) { // children first for (int i = 0; i < p_node->get_child_count(); i++) { Node *r = _post_fix_node(p_node->get_child(i), p_root, collision_map, r_scanned_meshes, p_node_data, p_material_data, p_animation_data, p_animation_fps); @@ -574,28 +575,35 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map> shapes; + const bool generate_collider = node_settings["generate/physics"]; + if (generate_collider) { + mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_MESH_AND_STATIC_COLLIDER; + if (node_settings.has("physics/body_type")) { + const BodyType body_type = (BodyType)node_settings["physics/body_type"].operator int(); + switch (body_type) { + case BODY_TYPE_STATIC: + mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_MESH_AND_STATIC_COLLIDER; + break; + case BODY_TYPE_DYNAMIC: + mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_RIGID_BODY_AND_MESH; + break; + case BODY_TYPE_AREA: + mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_AREA_ONLY; + break; + } + } + } + if (mesh_physics_mode != MeshPhysicsMode::MESH_PHYSICS_DISABLED) { + Vector> shapes; if (collision_map.has(m)) { shapes = collision_map[m]; } else { - switch (mesh_physics_mode) { - case MESH_PHYSICS_MESH_AND_STATIC_COLLIDER: { - _pre_gen_shape_list(m, shapes, false); - } break; - case MESH_PHYSICS_RIGID_BODY_AND_MESH: { - _pre_gen_shape_list(m, shapes, true); - } break; - case MESH_PHYSICS_STATIC_COLLIDER_ONLY: { - _pre_gen_shape_list(m, shapes, false); - } break; - case MESH_PHYSICS_AREA_ONLY: { - _pre_gen_shape_list(m, shapes, true); - } break; - } + shapes = get_collision_shapes( + m->get_mesh(), + node_settings); } if (shapes.size()) { @@ -604,13 +612,15 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Mapadd_child(col); + col->set_owner(p_node->get_owner()); + col->set_transform(get_collision_shapes_transform(node_settings)); base = col; } break; case MESH_PHYSICS_RIGID_BODY_AND_MESH: { RigidBody3D *rigid_body = memnew(RigidBody3D); rigid_body->set_name(p_node->get_name()); p_node->replace_by(rigid_body); - rigid_body->set_transform(mi->get_transform()); + rigid_body->set_transform(mi->get_transform() * get_collision_shapes_transform(node_settings)); p_node = rigid_body; mi->set_transform(Transform3D()); rigid_body->add_child(mi); @@ -619,7 +629,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Mapset_transform(mi->get_transform()); + col->set_transform(mi->get_transform() * get_collision_shapes_transform(node_settings)); col->set_name(p_node->get_name()); p_node->replace_by(col); memdelete(p_node); @@ -628,7 +638,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Mapset_transform(mi->get_transform()); + area->set_transform(mi->get_transform() * get_collision_shapes_transform(node_settings)); area->set_name(p_node->get_name()); p_node->replace_by(area); memdelete(p_node); @@ -928,8 +938,35 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p } break; case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: { r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/physics", PROPERTY_HINT_ENUM, "Disabled,Mesh + Static Collider,Rigid Body + Mesh,Static Collider Only,Area Only"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate/physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/navmesh", PROPERTY_HINT_ENUM, "Disabled,Mesh + NavMesh,NavMesh Only"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/body_type", PROPERTY_HINT_ENUM, "Static,Dynamic,Area"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/shape_type", PROPERTY_HINT_ENUM, "Decompose Convex,Simple Convex,Trimesh,Box,Sphere,Cylinder,Capsule", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); + + // Decomposition + Mesh::ConvexDecompositionSettings decomposition_default; + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/advanced", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/precision", PROPERTY_HINT_RANGE, "1,10,1"), 5)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/max_concavity", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.max_concavity)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/symmetry_planes_clipping_bias", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.symmetry_planes_clipping_bias)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/revolution_axes_clipping_bias", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.revolution_axes_clipping_bias)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/min_volume_per_convex_hull", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.min_volume_per_convex_hull)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/resolution", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.resolution)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/max_num_vertices_per_convex_hull", PROPERTY_HINT_RANGE, "5,512,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.max_num_vertices_per_convex_hull)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/plane_downsampling", PROPERTY_HINT_RANGE, "1,16,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.plane_downsampling)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/convexhull_downsampling", PROPERTY_HINT_RANGE, "1,16,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.convexhull_downsampling)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/normalize_mesh", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.normalize_mesh)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/mode", PROPERTY_HINT_ENUM, "Voxel,Tetrahedron", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), static_cast(decomposition_default.mode))); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/convexhull_approximation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.convexhull_approximation)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/max_convex_hulls", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.max_convex_hulls)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/project_hull_vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.project_hull_vertices)); + + // Primitives: Box, Sphere, Cylinder, Capsule. + r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3(2.0, 2.0, 2.0))); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "primitive/height", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1.0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "primitive/radius", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1.0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3())); + r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3())); } break; case INTERNAL_IMPORT_CATEGORY_MESH: { r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); @@ -980,6 +1017,65 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor case INTERNAL_IMPORT_CATEGORY_NODE: { } break; case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: { + const bool generate_physics = + p_options.has("generate/physics") && + p_options["generate/physics"].operator bool(); + + if ( + p_option == "physics/body_type" || + p_option == "physics/shape_type") { + // Show if need to generate collisions. + return generate_physics; + } + + if (p_option.find("decomposition/") >= 0) { + // Show if need to generate collisions. + if (generate_physics && + // Show if convex is enabled. + p_options["physics/shape_type"] == Variant(SHAPE_TYPE_DECOMPOSE_CONVEX)) { + if (p_option == "decomposition/advanced") { + return true; + } + + const bool decomposition_advanced = + p_options.has("decomposition/advanced") && + p_options["decomposition/advanced"].operator bool(); + + if (p_option == "decomposition/precision") { + return !decomposition_advanced; + } else { + return decomposition_advanced; + } + } + + return false; + } + + if (p_option == "primitive/position" || p_option == "primitive/rotation") { + const ShapeType physics_shape = (ShapeType)p_options["physics/shape_type"].operator int(); + return generate_physics && + physics_shape >= SHAPE_TYPE_BOX; + } + + if (p_option == "primitive/size") { + const ShapeType physics_shape = (ShapeType)p_options["physics/shape_type"].operator int(); + return generate_physics && + physics_shape == SHAPE_TYPE_BOX; + } + + if (p_option == "primitive/radius") { + const ShapeType physics_shape = (ShapeType)p_options["physics/shape_type"].operator int(); + return generate_physics && (physics_shape == SHAPE_TYPE_SPHERE || + physics_shape == SHAPE_TYPE_CYLINDER || + physics_shape == SHAPE_TYPE_CAPSULE); + } + + if (p_option == "primitive/height") { + const ShapeType physics_shape = (ShapeType)p_options["physics/shape_type"].operator int(); + return generate_physics && + (physics_shape == SHAPE_TYPE_CYLINDER || + physics_shape == SHAPE_TYPE_CAPSULE); + } } break; case INTERNAL_IMPORT_CATEGORY_MESH: { if (p_option == "save_to_file/path" || p_option == "save_to_file/make_streamable") { @@ -1016,6 +1112,33 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor return true; } +bool ResourceImporterScene::get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map &p_options) const { + switch (p_category) { + case INTERNAL_IMPORT_CATEGORY_NODE: { + } break; + case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: { + if ( + p_option == "generate/physics" || + p_option == "physics/shape_type" || + p_option.find("decomposition/") >= 0 || + p_option.find("primitive/") >= 0) { + return true; + } + } break; + case INTERNAL_IMPORT_CATEGORY_MESH: { + } break; + case INTERNAL_IMPORT_CATEGORY_MATERIAL: { + } break; + case INTERNAL_IMPORT_CATEGORY_ANIMATION: { + } break; + case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: { + } break; + default: { + } + } + return false; +} + void ResourceImporterScene::get_import_options(List *r_options, int p_preset) const { r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_type", PROPERTY_HINT_TYPE_STRING, "Node"), "Node3D")); r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_name"), "Scene Root")); @@ -1270,7 +1393,7 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m } } -void ResourceImporterScene::_add_shapes(Node *p_node, const List> &p_shapes) { +void ResourceImporterScene::_add_shapes(Node *p_node, const Vector> &p_shapes) { for (const Ref &E : p_shapes) { CollisionShape3D *cshape = memnew(CollisionShape3D); cshape->set_shape(E); @@ -1311,7 +1434,7 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file) { return nullptr; } - Map, List>> collision_map; + Map, Vector>> collision_map; _pre_fix_node(scene, scene, collision_map); @@ -1387,7 +1510,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p } Set> scanned_meshes; - Map, List>> collision_map; + Map, Vector>> collision_map; _pre_fix_node(scene, scene, collision_map); _post_fix_node(scene, scene, collision_map, scanned_meshes, node_data, material_data, animation_data, fps); diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index 781beff689..e5d9c235ca 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -58,7 +58,6 @@ public: IMPORT_FAIL_ON_MISSING_DEPENDENCIES = 4, IMPORT_GENERATE_TANGENT_ARRAYS = 8, IMPORT_USE_NAMED_SKIN_BINDS = 16, - }; virtual uint32_t get_import_flags() const; @@ -118,9 +117,25 @@ class ResourceImporterScene : public ResourceImporter { MESH_OVERRIDE_DISABLE, }; + enum BodyType { + BODY_TYPE_STATIC, + BODY_TYPE_DYNAMIC, + BODY_TYPE_AREA + }; + + enum ShapeType { + SHAPE_TYPE_DECOMPOSE_CONVEX, + SHAPE_TYPE_SIMPLE_CONVEX, + SHAPE_TYPE_TRIMESH, + SHAPE_TYPE_BOX, + SHAPE_TYPE_SPHERE, + SHAPE_TYPE_CYLINDER, + SHAPE_TYPE_CAPSULE, + }; + void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner); void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector &p_src_lightmap_cache, Vector> &r_lightmap_caches); - void _add_shapes(Node *p_node, const List> &p_shapes); + void _add_shapes(Node *p_node, const Vector> &p_shapes); public: static ResourceImporterScene *get_singleton() { return singleton; } @@ -152,14 +167,15 @@ public: void get_internal_import_options(InternalImportCategory p_category, List *r_options) const; bool get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map &p_options) const; + bool get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map &p_options) const; virtual void get_import_options(List *r_options, int p_preset = 0) const override; virtual bool get_option_visibility(const String &p_option, const Map &p_options) const override; // Import scenes *after* everything else (such as textures). virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; } - Node *_pre_fix_node(Node *p_node, Node *p_root, Map, List>> &collision_map); - Node *_post_fix_node(Node *p_node, Node *p_root, Map, List>> &collision_map, Set> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps); + Node *_pre_fix_node(Node *p_node, Node *p_root, Map, Vector>> &collision_map); + Node *_post_fix_node(Node *p_node, Node *p_root, Map, Vector>> &collision_map, Set> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps); Ref _save_animation_to_file(Ref anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks); void _create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all); @@ -177,6 +193,12 @@ public: virtual bool can_import_threaded() const override { return false; } ResourceImporterScene(); + + template + static Vector> get_collision_shapes(const Ref &p_mesh, const M &p_options); + + template + static Transform3D get_collision_shapes_transform(const M &p_options); }; class EditorSceneImporterESCN : public EditorSceneImporter { @@ -189,4 +211,176 @@ public: virtual Ref import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) override; }; +#include "scene/resources/box_shape_3d.h" +#include "scene/resources/capsule_shape_3d.h" +#include "scene/resources/cylinder_shape_3d.h" +#include "scene/resources/sphere_shape_3d.h" + +template +Vector> ResourceImporterScene::get_collision_shapes(const Ref &p_mesh, const M &p_options) { + ShapeType generate_shape_type = SHAPE_TYPE_DECOMPOSE_CONVEX; + if (p_options.has(SNAME("physics/shape_type"))) { + generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int(); + } + + if (generate_shape_type == SHAPE_TYPE_DECOMPOSE_CONVEX) { + Mesh::ConvexDecompositionSettings decomposition_settings; + bool advanced = false; + if (p_options.has(SNAME("decomposition/advanced"))) { + advanced = p_options[SNAME("decomposition/advanced")]; + } + + if (advanced) { + if (p_options.has(SNAME("decomposition/max_concavity"))) { + decomposition_settings.max_concavity = p_options[SNAME("decomposition/max_concavity")]; + } + + if (p_options.has(SNAME("decomposition/symmetry_planes_clipping_bias"))) { + decomposition_settings.symmetry_planes_clipping_bias = p_options[SNAME("decomposition/symmetry_planes_clipping_bias")]; + } + + if (p_options.has(SNAME("decomposition/revolution_axes_clipping_bias"))) { + decomposition_settings.revolution_axes_clipping_bias = p_options[SNAME("decomposition/revolution_axes_clipping_bias")]; + } + + if (p_options.has(SNAME("decomposition/min_volume_per_convex_hull"))) { + decomposition_settings.min_volume_per_convex_hull = p_options[SNAME("decomposition/min_volume_per_convex_hull")]; + } + + if (p_options.has(SNAME("decomposition/resolution"))) { + decomposition_settings.resolution = p_options[SNAME("decomposition/resolution")]; + } + + if (p_options.has(SNAME("decomposition/max_num_vertices_per_convex_hull"))) { + decomposition_settings.max_num_vertices_per_convex_hull = p_options[SNAME("decomposition/max_num_vertices_per_convex_hull")]; + } + + if (p_options.has(SNAME("decomposition/plane_downsampling"))) { + decomposition_settings.plane_downsampling = p_options[SNAME("decomposition/plane_downsampling")]; + } + + if (p_options.has(SNAME("decomposition/convexhull_downsampling"))) { + decomposition_settings.convexhull_downsampling = p_options[SNAME("decomposition/convexhull_downsampling")]; + } + + if (p_options.has(SNAME("decomposition/normalize_mesh"))) { + decomposition_settings.normalize_mesh = p_options[SNAME("decomposition/normalize_mesh")]; + } + + if (p_options.has(SNAME("decomposition/mode"))) { + decomposition_settings.mode = (Mesh::ConvexDecompositionSettings::Mode)p_options[SNAME("decomposition/mode")].operator int(); + } + + if (p_options.has(SNAME("decomposition/convexhull_approximation"))) { + decomposition_settings.convexhull_approximation = p_options[SNAME("decomposition/convexhull_approximation")]; + } + + if (p_options.has(SNAME("decomposition/max_convex_hulls"))) { + decomposition_settings.max_convex_hulls = p_options[SNAME("decomposition/max_convex_hulls")]; + } + + if (p_options.has(SNAME("decomposition/project_hull_vertices"))) { + decomposition_settings.project_hull_vertices = p_options[SNAME("decomposition/project_hull_vertices")]; + } + } else { + int precision_level = 5; + if (p_options.has(SNAME("decomposition/precision"))) { + precision_level = p_options[SNAME("decomposition/precision")]; + } + + const real_t precision = real_t(precision_level - 1) / 9.0; + + decomposition_settings.max_concavity = Math::lerp(real_t(1.0), real_t(0.001), precision); + decomposition_settings.min_volume_per_convex_hull = Math::lerp(real_t(0.01), real_t(0.0001), precision); + decomposition_settings.resolution = Math::lerp(10'000, 100'000, precision); + decomposition_settings.max_num_vertices_per_convex_hull = Math::lerp(32, 64, precision); + decomposition_settings.plane_downsampling = Math::lerp(3, 16, precision); + decomposition_settings.convexhull_downsampling = Math::lerp(3, 16, precision); + decomposition_settings.max_convex_hulls = Math::lerp(1, 32, precision); + } + + return p_mesh->convex_decompose(decomposition_settings); + } else if (generate_shape_type == SHAPE_TYPE_SIMPLE_CONVEX) { + Vector> shapes; + shapes.push_back(p_mesh->create_convex_shape(true, /*Passing false, otherwise VHACD will be used to simplify (Decompose) the Mesh.*/ false)); + return shapes; + } else if (generate_shape_type == SHAPE_TYPE_TRIMESH) { + Vector> shapes; + shapes.push_back(p_mesh->create_trimesh_shape()); + return shapes; + } else if (generate_shape_type == SHAPE_TYPE_BOX) { + Ref box; + box.instantiate(); + if (p_options.has(SNAME("primitive/size"))) { + box->set_size(p_options[SNAME("primitive/size")]); + } + + Vector> shapes; + shapes.push_back(box); + return shapes; + + } else if (generate_shape_type == SHAPE_TYPE_SPHERE) { + Ref sphere; + sphere.instantiate(); + if (p_options.has(SNAME("primitive/radius"))) { + sphere->set_radius(p_options[SNAME("primitive/radius")]); + } + + Vector> shapes; + shapes.push_back(sphere); + return shapes; + } else if (generate_shape_type == SHAPE_TYPE_CYLINDER) { + Ref cylinder; + cylinder.instantiate(); + if (p_options.has(SNAME("primitive/height"))) { + cylinder->set_height(p_options[SNAME("primitive/height")]); + } + if (p_options.has(SNAME("primitive/radius"))) { + cylinder->set_radius(p_options[SNAME("primitive/radius")]); + } + + Vector> shapes; + shapes.push_back(cylinder); + return shapes; + } else if (generate_shape_type == SHAPE_TYPE_CAPSULE) { + Ref capsule; + capsule.instantiate(); + if (p_options.has(SNAME("primitive/height"))) { + capsule->set_height(p_options[SNAME("primitive/height")]); + } + if (p_options.has(SNAME("primitive/radius"))) { + capsule->set_radius(p_options[SNAME("primitive/radius")]); + } + + Vector> shapes; + shapes.push_back(capsule); + return shapes; + } + return Vector>(); +} + +template +Transform3D ResourceImporterScene::get_collision_shapes_transform(const M &p_options) { + Transform3D transform; + + ShapeType generate_shape_type = SHAPE_TYPE_DECOMPOSE_CONVEX; + if (p_options.has(SNAME("physics/shape_type"))) { + generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int(); + } + + if (generate_shape_type == SHAPE_TYPE_BOX || + generate_shape_type == SHAPE_TYPE_SPHERE || + generate_shape_type == SHAPE_TYPE_CYLINDER || + generate_shape_type == SHAPE_TYPE_CAPSULE) { + if (p_options.has(SNAME("primitive/position"))) { + transform.origin = p_options[SNAME("primitive/position")]; + } + + if (p_options.has(SNAME("primitive/rotation"))) { + transform.basis.set_euler((p_options[SNAME("primitive/rotation")].operator Vector3() / 180.0) * Math_PI); + } + } + return transform; +} + #endif // RESOURCEIMPORTERSCENE_H diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp index 19a8f209bb..4bcb6863fb 100644 --- a/editor/import/scene_import_settings.cpp +++ b/editor/import/scene_import_settings.cpp @@ -53,6 +53,11 @@ class SceneImportSettingsData : public Object { } current[p_name] = p_value; + + if (ResourceImporterScene::get_singleton()->get_internal_option_update_view_required(category, p_name, current)) { + SceneImportSettings::get_singleton()->update_view(); + } + return true; } return false; @@ -317,6 +322,13 @@ void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) { if (mesh_node && mesh_node->get_mesh().is_valid()) { _fill_mesh(scene_tree, mesh_node->get_mesh(), item); + // Add the collider view. + MeshInstance3D *collider_view = memnew(MeshInstance3D); + collider_view->set_name("collider_view"); + collider_view->set_visible(false); + mesh_node->add_child(collider_view); + collider_view->set_owner(mesh_node); + Transform3D accum_xform; Node3D *base = mesh_node; while (base) { @@ -346,6 +358,54 @@ void SceneImportSettings::_update_scene() { _fill_scene(scene, nullptr); } +void SceneImportSettings::_update_view_gizmos() { + for (const KeyValue &e : node_map) { + bool generate_collider = false; + if (e.value.settings.has(SNAME("generate/physics"))) { + generate_collider = e.value.settings[SNAME("generate/physics")]; + } + + MeshInstance3D *mesh_node = Object::cast_to(e.value.node); + if (mesh_node == nullptr || mesh_node->get_mesh().is_null()) { + // Nothing to do + continue; + } + + MeshInstance3D *collider_view = static_cast(mesh_node->find_node("collider_view")); + CRASH_COND_MSG(collider_view == nullptr, "This is unreachable, since the collider view is always created even when the collision is not used! If this is triggered there is a bug on the function `_fill_scene`."); + + collider_view->set_visible(generate_collider); + if (generate_collider) { + // This collider_view doesn't have a mesh so we need to generate a new one. + + // Generate the mesh collider. + Vector> shapes = ResourceImporterScene::get_collision_shapes(mesh_node->get_mesh(), e.value.settings); + const Transform3D transform = ResourceImporterScene::get_collision_shapes_transform(e.value.settings); + + Ref collider_view_mesh; + collider_view_mesh.instantiate(); + for (Ref shape : shapes) { + Ref debug_shape_mesh; + if (shape.is_valid()) { + debug_shape_mesh = shape->get_debug_mesh(); + } + if (debug_shape_mesh.is_valid()) { + collider_view_mesh->add_surface_from_arrays( + debug_shape_mesh->surface_get_primitive_type(0), + debug_shape_mesh->surface_get_arrays(0)); + + collider_view_mesh->surface_set_material( + collider_view_mesh->get_surface_count() - 1, + collider_mat); + } + } + + collider_view->set_mesh(collider_view_mesh); + collider_view->set_transform(transform); + } + } +} + void SceneImportSettings::_update_camera() { AABB camera_aabb; @@ -404,11 +464,16 @@ void SceneImportSettings::_load_default_subresource_settings(Mapsettings = nullptr; scene = ResourceImporterScene::get_singleton()->pre_import(p_path); if (scene == nullptr) { EditorNode::get_singleton()->show_warning(TTR("Error opening scene")); @@ -463,6 +528,7 @@ void SceneImportSettings::open_settings(const String &p_path) { } popup_centered_ratio(); + _update_view_gizmos(); _update_camera(); set_title(vformat(TTR("Advanced Import Settings for '%s'"), base_path.get_file())); @@ -629,6 +695,7 @@ void SceneImportSettings::_material_tree_selected() { _select(material_tree, type, import_id); } + void SceneImportSettings::_mesh_tree_selected() { if (selecting) { return; @@ -640,6 +707,7 @@ void SceneImportSettings::_mesh_tree_selected() { _select(mesh_tree, type, import_id); } + void SceneImportSettings::_scene_tree_selected() { if (selecting) { return; @@ -1144,6 +1212,12 @@ SceneImportSettings::SceneImportSettings() { material_preview.instantiate(); } + { + collider_mat.instantiate(); + collider_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + collider_mat->set_albedo(Color(0.5, 0.5, 1.0)); + } + inspector = memnew(EditorInspector); inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); diff --git a/editor/import/scene_import_settings.h b/editor/import/scene_import_settings.h index ddcf4a6d5d..c7c94af493 100644 --- a/editor/import/scene_import_settings.h +++ b/editor/import/scene_import_settings.h @@ -84,6 +84,8 @@ class SceneImportSettings : public ConfirmationDialog { MeshInstance3D *mesh_preview; Ref material_preview; + Ref collider_mat; + float cam_rot_x; float cam_rot_y; float cam_zoom; @@ -145,6 +147,7 @@ class SceneImportSettings : public ConfirmationDialog { bool selecting = false; + void _update_view_gizmos(); void _update_camera(); void _select(Tree *p_from, String p_type, String p_id); void _material_tree_selected(); @@ -190,6 +193,7 @@ protected: void _notification(int p_what); public: + void update_view(); void open_settings(const String &p_path); static SceneImportSettings *get_singleton(); SceneImportSettings(); diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp index 0d14347225..45a62ea003 100644 --- a/editor/import/scene_importer_mesh.cpp +++ b/editor/import/scene_importer_mesh.cpp @@ -501,12 +501,12 @@ Vector EditorSceneImporterMesh::get_faces() const { return faces; } -Vector> EditorSceneImporterMesh::convex_decompose() const { +Vector> EditorSceneImporterMesh::convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const { ERR_FAIL_COND_V(!Mesh::convex_composition_function, Vector>()); const Vector faces = get_faces(); - Vector> decomposed = Mesh::convex_composition_function(faces, -1); + Vector> decomposed = Mesh::convex_composition_function(faces, p_settings); Vector> ret; diff --git a/editor/import/scene_importer_mesh.h b/editor/import/scene_importer_mesh.h index 2488de7ed0..da1eb5b490 100644 --- a/editor/import/scene_importer_mesh.h +++ b/editor/import/scene_importer_mesh.h @@ -103,7 +103,7 @@ public: Ref get_shadow_mesh() const; Vector get_faces() const; - Vector> convex_decompose() const; + Vector> convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const; Ref create_trimesh_shape() const; Ref create_navigation_mesh(); Error lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector &p_src_cache, Vector &r_dst_cache); diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp index 9a2b222f21..574d3ef27e 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp +++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp @@ -202,7 +202,8 @@ void MeshInstance3DEditor::_menu_option(int p_option) { return; } - Vector> shapes = mesh->convex_decompose(); + Mesh::ConvexDecompositionSettings settings; + Vector> shapes = mesh->convex_decompose(settings); if (!shapes.size()) { err_dialog->set_text(TTR("Couldn't create any collision shapes.")); diff --git a/modules/vhacd/register_types.cpp b/modules/vhacd/register_types.cpp index 2b48e94604..88b2a568ea 100644 --- a/modules/vhacd/register_types.cpp +++ b/modules/vhacd/register_types.cpp @@ -32,7 +32,23 @@ #include "scene/resources/mesh.h" #include "thirdparty/vhacd/public/VHACD.h" -static Vector> convex_decompose(const Vector &p_faces, int p_max_convex_hulls = -1) { +static Vector> convex_decompose(const Vector &p_faces, const Mesh::ConvexDecompositionSettings &p_settings) { + VHACD::IVHACD::Parameters params; + params.m_concavity = p_settings.max_concavity; + params.m_alpha = p_settings.symmetry_planes_clipping_bias; + params.m_beta = p_settings.revolution_axes_clipping_bias; + params.m_minVolumePerCH = p_settings.min_volume_per_convex_hull; + params.m_resolution = p_settings.resolution; + params.m_maxNumVerticesPerCH = p_settings.max_num_vertices_per_convex_hull; + params.m_planeDownsampling = p_settings.plane_downsampling; + params.m_convexhullDownsampling = p_settings.convexhull_downsampling; + params.m_pca = p_settings.normalize_mesh; + params.m_mode = p_settings.mode; + params.m_convexhullApproximation = p_settings.convexhull_approximation; + params.m_oclAcceleration = true; + params.m_maxConvexHulls = p_settings.max_convex_hulls; + params.m_projectHullVertices = p_settings.project_hull_vertices; + Vector vertices; vertices.resize(p_faces.size() * 9); Vector indices; @@ -47,11 +63,6 @@ static Vector> convex_decompose(const Vector &p_faces, int } } - VHACD::IVHACD::Parameters params; - if (p_max_convex_hulls > 0) { - params.m_maxConvexHulls = p_max_convex_hulls; - } - VHACD::IVHACD *decomposer = VHACD::CreateVHACD(); decomposer->Compute(vertices.ptr(), vertices.size() / 3, indices.ptr(), indices.size() / 3, params); diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index de6925244a..7e7db57af3 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -274,7 +274,8 @@ Node *MeshInstance3D::create_multiple_convex_collisions_node() { return nullptr; } - Vector> shapes = mesh->convex_decompose(); + Mesh::ConvexDecompositionSettings settings; + Vector> shapes = mesh->convex_decompose(settings); if (!shapes.size()) { return nullptr; } diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index ad589a605e..4f301fca95 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -224,7 +224,9 @@ Vector Mesh::get_faces() const { Ref Mesh::create_convex_shape(bool p_clean, bool p_simplify) const { if (p_simplify) { - Vector> decomposed = convex_decompose(1); + ConvexDecompositionSettings settings; + settings.max_convex_hulls = 1; + Vector> decomposed = convex_decompose(settings); if (decomposed.size() == 1) { return decomposed[0]; } else { @@ -564,12 +566,12 @@ void Mesh::clear_cache() const { debug_lines.clear(); } -Vector> Mesh::convex_decompose(int p_max_convex_hulls) const { +Vector> Mesh::convex_decompose(const ConvexDecompositionSettings &p_settings) const { ERR_FAIL_COND_V(!convex_composition_function, Vector>()); const Vector faces = get_faces(); - Vector> decomposed = convex_composition_function(faces, p_max_convex_hulls); + const Vector> decomposed = convex_composition_function(faces, p_settings); Vector> ret; diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 27b0eb098b..240182361f 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -159,11 +159,42 @@ public: Size2i get_lightmap_size_hint() const; void clear_cache() const; - typedef Vector> (*ConvexDecompositionFunc)(const Vector &p_faces, int p_max_convex_hulls); + struct ConvexDecompositionSettings { + enum Mode : int { + CONVEX_DECOMPOSITION_MODE_VOXEL = 0, + CONVEX_DECOMPOSITION_MODE_TETRAHEDRON + }; + + /// Maximum concavity. [Range: 0.0 -> 1.0] + real_t max_concavity = 1.0; + /// Controls the bias toward clipping along symmetry planes. [Range: 0.0 -> 1.0] + real_t symmetry_planes_clipping_bias = 0.05; + /// Controls the bias toward clipping along revolution axes. [Range: 0.0 -> 1.0] + real_t revolution_axes_clipping_bias = 0.05; + real_t min_volume_per_convex_hull = 0.0001; + /// Maximum number of voxels generated during the voxelization stage. + uint32_t resolution = 10'000; + uint32_t max_num_vertices_per_convex_hull = 32; + /// Controls the granularity of the search for the "best" clipping plane. + /// [Range: 1 -> 16] + uint32_t plane_downsampling = 4; + /// Controls the precision of the convex-hull generation process during the + /// clipping plane selection stage. + /// [Range: 1 -> 16] + uint32_t convexhull_downsampling = 4; + /// enable/disable normalizing the mesh before applying the convex decomposition. + bool normalize_mesh = false; + Mode mode = CONVEX_DECOMPOSITION_MODE_VOXEL; + bool convexhull_approximation = true; + /// This is the maximum number of convex hulls to produce from the merge operation. + uint32_t max_convex_hulls = 1; + bool project_hull_vertices = true; + }; + typedef Vector> (*ConvexDecompositionFunc)(const Vector &p_faces, const ConvexDecompositionSettings &p_settings); static ConvexDecompositionFunc convex_composition_function; - Vector> convex_decompose(int p_max_convex_hulls = -1) const; + Vector> convex_decompose(const ConvexDecompositionSettings &p_settings) const; virtual int get_builtin_bind_pose_count() const; virtual Transform3D get_builtin_bind_pose(int p_index) const;