Browse Source

Improve collision generation usability in the new 3D scene import workflow.

With this PR it's possible to add a collision during the Mesh import, directly in editor.
To generate the shape is possible to chose between the following options:
- Decompose Convex: The Mesh is decomposed in one or many Convex Shapes (Using the VHACD library).
- Simple Convex: Is generated a convex shape that enclose the entire mesh.
- Trimesh: Generate a trimesh shape using the Mesh faces.
- Box: Add a primitive box shape, where you can tweak the `size`, `position`, `rotation`.
- Sphere: Add a primitive sphere shape, where you can tweak the `radius`, `position`, `rotation`.
- Cylinder: Add a primitive cylinder shape, where you can tweak the `height`, `radius`, `position`, `rotation`.
- Capsule: Add a primitive capsule shape, where you can tweak the `height`, `radius`, `position`, `rotation`.

It's also possible to chose the generated body, so you can create:
- Rigid Body
- Static Body
- Area
AndreaCatania 4 years ago
parent
commit
e3a06c3a9e

+ 156 - 33
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<EditorSceneImporterMesh> &mesh, List<Ref<Shape3D>> &r_shape_list, bool p_convex) {
+static void _pre_gen_shape_list(Ref<EditorSceneImporterMesh> &mesh, Vector<Ref<Shape3D>> &r_shape_list, bool p_convex) {
 	ERR_FAIL_NULL_MSG(mesh, "Cannot generate shape list with null mesh value");
 	if (!p_convex) {
 		Ref<Shape3D> shape = mesh->create_trimesh_shape();
 		r_shape_list.push_back(shape);
 	} else {
-		Vector<Ref<Shape3D>> cd = mesh->convex_decompose();
+		Vector<Ref<Shape3D>> 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<EditorSceneImporterMesh> &mesh, List<R
 	}
 }
 
-Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map) {
+Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &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<Ref<E
 			Ref<EditorSceneImporterMesh> mesh = mi->get_mesh();
 
 			if (mesh.is_valid()) {
-				List<Ref<Shape3D>> shapes;
+				Vector<Ref<Shape3D>> 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<Ref<E
 		Ref<EditorSceneImporterMesh> mesh = mi->get_mesh();
 
 		if (mesh.is_valid()) {
-			List<Ref<Shape3D>> shapes;
+			Vector<Ref<Shape3D>> 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<Ref<E
 		Ref<EditorSceneImporterMesh> mesh = mi->get_mesh();
 
 		if (mesh.is_valid()) {
-			List<Ref<Shape3D>> shapes;
+			Vector<Ref<Shape3D>> 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<Ref<E
 
 		Ref<EditorSceneImporterMesh> mesh = mi->get_mesh();
 		if (!mesh.is_null()) {
-			List<Ref<Shape3D>> shapes;
+			Vector<Ref<Shape3D>> 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<Ref<E
 	return p_node;
 }
 
-Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &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<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &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<Ref<
 			}
 
 			if (node_settings.has("generate/physics")) {
-				int mesh_physics_mode = node_settings["generate/physics"];
-
-				if (mesh_physics_mode != MESH_PHYSICS_DISABLED) {
-					List<Ref<Shape3D>> shapes;
+				int mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_DISABLED;
+
+				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<Ref<Shape3D>> 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, Map<Ref<
 							case MESH_PHYSICS_MESH_AND_STATIC_COLLIDER: {
 								StaticBody3D *col = memnew(StaticBody3D);
 								p_node->add_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, Map<Ref<
 							} break;
 							case MESH_PHYSICS_STATIC_COLLIDER_ONLY: {
 								StaticBody3D *col = memnew(StaticBody3D);
-								col->set_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, Map<Ref<
 							} break;
 							case MESH_PHYSICS_AREA_ONLY: {
 								Area3D *area = memnew(Area3D);
-								area->set_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<int>(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<StringName, Variant> &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<ImportOption> *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<Ref<Shape3D>> &p_shapes) {
+void ResourceImporterScene::_add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes) {
 	for (const Ref<Shape3D> &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<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> collision_map;
+	Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> 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<Ref<EditorSceneImporterMesh>> scanned_meshes;
-	Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> collision_map;
+	Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> 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);

+ 198 - 4
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<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);
-	void _add_shapes(Node *p_node, const List<Ref<Shape3D>> &p_shapes);
+	void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes);
 
 public:
 	static ResourceImporterScene *get_singleton() { return singleton; }
@@ -152,14 +167,15 @@ public:
 
 	void get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const;
 	bool get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const;
+	bool get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const;
 
 	virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
 	virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &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<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map);
-	Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &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<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map);
+	Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps);
 
 	Ref<Animation> _save_animation_to_file(Ref<Animation> 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 <class M>
+	static Vector<Ref<Shape3D>> get_collision_shapes(const Ref<Mesh> &p_mesh, const M &p_options);
+
+	template <class M>
+	static Transform3D get_collision_shapes_transform(const M &p_options);
 };
 
 class EditorSceneImporterESCN : public EditorSceneImporter {
@@ -189,4 +211,176 @@ public:
 	virtual Ref<Animation> 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 <class M>
+Vector<Ref<Shape3D>> ResourceImporterScene::get_collision_shapes(const Ref<Mesh> &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<Ref<Shape3D>> 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<Ref<Shape3D>> shapes;
+		shapes.push_back(p_mesh->create_trimesh_shape());
+		return shapes;
+	} else if (generate_shape_type == SHAPE_TYPE_BOX) {
+		Ref<BoxShape3D> box;
+		box.instantiate();
+		if (p_options.has(SNAME("primitive/size"))) {
+			box->set_size(p_options[SNAME("primitive/size")]);
+		}
+
+		Vector<Ref<Shape3D>> shapes;
+		shapes.push_back(box);
+		return shapes;
+
+	} else if (generate_shape_type == SHAPE_TYPE_SPHERE) {
+		Ref<SphereShape3D> sphere;
+		sphere.instantiate();
+		if (p_options.has(SNAME("primitive/radius"))) {
+			sphere->set_radius(p_options[SNAME("primitive/radius")]);
+		}
+
+		Vector<Ref<Shape3D>> shapes;
+		shapes.push_back(sphere);
+		return shapes;
+	} else if (generate_shape_type == SHAPE_TYPE_CYLINDER) {
+		Ref<CylinderShape3D> 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<Ref<Shape3D>> shapes;
+		shapes.push_back(cylinder);
+		return shapes;
+	} else if (generate_shape_type == SHAPE_TYPE_CAPSULE) {
+		Ref<CapsuleShape3D> 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<Ref<Shape3D>> shapes;
+		shapes.push_back(capsule);
+		return shapes;
+	}
+	return Vector<Ref<Shape3D>>();
+}
+
+template <class M>
+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

+ 74 - 0
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<String, NodeData> &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<MeshInstance3D>(e.value.node);
+		if (mesh_node == nullptr || mesh_node->get_mesh().is_null()) {
+			// Nothing to do
+			continue;
+		}
+
+		MeshInstance3D *collider_view = static_cast<MeshInstance3D *>(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<Ref<Shape3D>> shapes = ResourceImporterScene::get_collision_shapes(mesh_node->get_mesh(), e.value.settings);
+			const Transform3D transform = ResourceImporterScene::get_collision_shapes_transform(e.value.settings);
+
+			Ref<ArrayMesh> collider_view_mesh;
+			collider_view_mesh.instantiate();
+			for (Ref<Shape3D> shape : shapes) {
+				Ref<ArrayMesh> 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(Map<StringName, Var
 	}
 }
 
+void SceneImportSettings::update_view() {
+	_update_view_gizmos();
+}
+
 void SceneImportSettings::open_settings(const String &p_path) {
 	if (scene) {
 		memdelete(scene);
 		scene = nullptr;
 	}
+	scene_import_settings_data->settings = 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));
 

+ 4 - 0
editor/import/scene_import_settings.h

@@ -84,6 +84,8 @@ class SceneImportSettings : public ConfirmationDialog {
 	MeshInstance3D *mesh_preview;
 	Ref<SphereMesh> material_preview;
 
+	Ref<StandardMaterial3D> 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();

+ 2 - 2
editor/import/scene_importer_mesh.cpp

@@ -501,12 +501,12 @@ Vector<Face3> EditorSceneImporterMesh::get_faces() const {
 	return faces;
 }
 
-Vector<Ref<Shape3D>> EditorSceneImporterMesh::convex_decompose() const {
+Vector<Ref<Shape3D>> EditorSceneImporterMesh::convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const {
 	ERR_FAIL_COND_V(!Mesh::convex_composition_function, Vector<Ref<Shape3D>>());
 
 	const Vector<Face3> faces = get_faces();
 
-	Vector<Vector<Face3>> decomposed = Mesh::convex_composition_function(faces, -1);
+	Vector<Vector<Face3>> decomposed = Mesh::convex_composition_function(faces, p_settings);
 
 	Vector<Ref<Shape3D>> ret;
 

+ 1 - 1
editor/import/scene_importer_mesh.h

@@ -103,7 +103,7 @@ public:
 	Ref<EditorSceneImporterMesh> get_shadow_mesh() const;
 
 	Vector<Face3> get_faces() const;
-	Vector<Ref<Shape3D>> convex_decompose() const;
+	Vector<Ref<Shape3D>> convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const;
 	Ref<Shape3D> create_trimesh_shape() const;
 	Ref<NavigationMesh> create_navigation_mesh();
 	Error lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache);

+ 2 - 1
editor/plugins/mesh_instance_3d_editor_plugin.cpp

@@ -202,7 +202,8 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
 				return;
 			}
 
-			Vector<Ref<Shape3D>> shapes = mesh->convex_decompose();
+			Mesh::ConvexDecompositionSettings settings;
+			Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(settings);
 
 			if (!shapes.size()) {
 				err_dialog->set_text(TTR("Couldn't create any collision shapes."));

+ 17 - 6
modules/vhacd/register_types.cpp

@@ -32,7 +32,23 @@
 #include "scene/resources/mesh.h"
 #include "thirdparty/vhacd/public/VHACD.h"
 
-static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &p_faces, int p_max_convex_hulls = -1) {
+static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &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<real_t> vertices;
 	vertices.resize(p_faces.size() * 9);
 	Vector<uint32_t> indices;
@@ -47,11 +63,6 @@ static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &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);
 

+ 2 - 1
scene/3d/mesh_instance_3d.cpp

@@ -274,7 +274,8 @@ Node *MeshInstance3D::create_multiple_convex_collisions_node() {
 		return nullptr;
 	}
 
-	Vector<Ref<Shape3D>> shapes = mesh->convex_decompose();
+	Mesh::ConvexDecompositionSettings settings;
+	Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(settings);
 	if (!shapes.size()) {
 		return nullptr;
 	}

+ 5 - 3
scene/resources/mesh.cpp

@@ -224,7 +224,9 @@ Vector<Face3> Mesh::get_faces() const {
 
 Ref<Shape3D> Mesh::create_convex_shape(bool p_clean, bool p_simplify) const {
 	if (p_simplify) {
-		Vector<Ref<Shape3D>> decomposed = convex_decompose(1);
+		ConvexDecompositionSettings settings;
+		settings.max_convex_hulls = 1;
+		Vector<Ref<Shape3D>> 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<Ref<Shape3D>> Mesh::convex_decompose(int p_max_convex_hulls) const {
+Vector<Ref<Shape3D>> Mesh::convex_decompose(const ConvexDecompositionSettings &p_settings) const {
 	ERR_FAIL_COND_V(!convex_composition_function, Vector<Ref<Shape3D>>());
 
 	const Vector<Face3> faces = get_faces();
 
-	Vector<Vector<Face3>> decomposed = convex_composition_function(faces, p_max_convex_hulls);
+	const Vector<Vector<Face3>> decomposed = convex_composition_function(faces, p_settings);
 
 	Vector<Ref<Shape3D>> ret;
 

+ 33 - 2
scene/resources/mesh.h

@@ -159,11 +159,42 @@ public:
 	Size2i get_lightmap_size_hint() const;
 	void clear_cache() const;
 
-	typedef Vector<Vector<Face3>> (*ConvexDecompositionFunc)(const Vector<Face3> &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<Vector<Face3>> (*ConvexDecompositionFunc)(const Vector<Face3> &p_faces, const ConvexDecompositionSettings &p_settings);
 
 	static ConvexDecompositionFunc convex_composition_function;
 
-	Vector<Ref<Shape3D>> convex_decompose(int p_max_convex_hulls = -1) const;
+	Vector<Ref<Shape3D>> 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;