Options to clean/simplify convex hull generated from mesh

Clean: remove duplicate and interior vertices (uses Bullet algorithm)
Simplify: modify the geometry for further simplification (uses VHACD
algorithm)

In the editor, single convex hull now uses the clean option.
Added a new editor entry to create a simplified convex hull, can be
useful for creating convex hull from highly tessellated triangle meshes.
This commit is contained in:
PouleyKetchoupp 2021-07-07 12:14:12 -07:00
parent 56d7126864
commit 1de995ae99
10 changed files with 71 additions and 22 deletions

View file

@ -16,8 +16,14 @@
<method name="create_convex_shape" qualifiers="const"> <method name="create_convex_shape" qualifiers="const">
<return type="Shape3D"> <return type="Shape3D">
</return> </return>
<argument index="0" name="clean" type="bool" default="true">
</argument>
<argument index="1" name="simplify" type="bool" default="false">
</argument>
<description> <description>
Calculate a [ConvexPolygonShape3D] from the mesh. Calculate a [ConvexPolygonShape3D] from the mesh.
If [code]clean[/code] is [code]true[/code] (default), duplicate and interior vertices are removed automatically. You can set it to [code]false[/code] to make the process faster if not needed.
If [code]simplify[/code] is [code]true[/code], the geometry can be further simplified to reduce the amount of vertices. Disabled by default.
</description> </description>
</method> </method>
<method name="create_outline" qualifiers="const"> <method name="create_outline" qualifiers="const">

View file

@ -16,8 +16,14 @@
<method name="create_convex_collision"> <method name="create_convex_collision">
<return type="void"> <return type="void">
</return> </return>
<argument index="0" name="clean" type="bool" default="true">
</argument>
<argument index="1" name="simplify" type="bool" default="false">
</argument>
<description> <description>
This helper creates a [StaticBody3D] child node with a [ConvexPolygonShape3D] collision shape calculated from the mesh geometry. It's mainly used for testing. This helper creates a [StaticBody3D] child node with a [ConvexPolygonShape3D] collision shape calculated from the mesh geometry. It's mainly used for testing.
If [code]clean[/code] is [code]true[/code] (default), duplicate and interior vertices are removed automatically. You can set it to [code]false[/code] to make the process faster if not needed.
If [code]simplify[/code] is [code]true[/code], the geometry can be further simplified to reduce the amount of vertices. Disabled by default.
</description> </description>
</method> </method>
<method name="create_debug_tangents"> <method name="create_debug_tangents">

View file

@ -506,7 +506,7 @@ Vector<Ref<Shape3D>> EditorSceneImporterMesh::convex_decompose() const {
const Vector<Face3> faces = get_faces(); const Vector<Face3> faces = get_faces();
Vector<Vector<Face3>> decomposed = Mesh::convex_composition_function(faces); Vector<Vector<Face3>> decomposed = Mesh::convex_composition_function(faces, -1);
Vector<Ref<Shape3D>> ret; Vector<Ref<Shape3D>> ret;

View file

@ -153,14 +153,18 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
ur->add_undo_method(node->get_parent(), "remove_child", cshape); ur->add_undo_method(node->get_parent(), "remove_child", cshape);
ur->commit_action(); ur->commit_action();
} break; } break;
case MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE: {
case MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE:
case MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE: {
if (node == get_tree()->get_edited_scene_root()) { if (node == get_tree()->get_edited_scene_root()) {
err_dialog->set_text(TTR("Can't create a single convex collision shape for the scene root.")); err_dialog->set_text(TTR("Can't create a single convex collision shape for the scene root."));
err_dialog->popup_centered(); err_dialog->popup_centered();
return; return;
} }
Ref<Shape3D> shape = mesh->create_convex_shape(); bool simplify = (p_option == MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE);
Ref<Shape3D> shape = mesh->create_convex_shape(true, simplify);
if (shape.is_null()) { if (shape.is_null()) {
err_dialog->set_text(TTR("Couldn't create a single convex collision shape.")); err_dialog->set_text(TTR("Couldn't create a single convex collision shape."));
@ -169,7 +173,11 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
} }
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
ur->create_action(TTR("Create Single Convex Shape")); if (simplify) {
ur->create_action(TTR("Create Simplified Convex Shape"));
} else {
ur->create_action(TTR("Create Single Convex Shape"));
}
CollisionShape3D *cshape = memnew(CollisionShape3D); CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shape); cshape->set_shape(shape);
@ -186,6 +194,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
ur->commit_action(); ur->commit_action();
} break; } break;
case MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES: { case MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES: {
if (node == get_tree()->get_edited_scene_root()) { if (node == get_tree()->get_edited_scene_root()) {
err_dialog->set_text(TTR("Can't create multiple convex collision shapes for the scene root.")); err_dialog->set_text(TTR("Can't create multiple convex collision shapes for the scene root."));
@ -441,8 +450,10 @@ MeshInstance3DEditor::MeshInstance3DEditor() {
options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection.")); options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection."));
options->get_popup()->add_item(TTR("Create Single Convex Collision Sibling"), MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE); options->get_popup()->add_item(TTR("Create Single Convex Collision Sibling"), MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE);
options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection.")); options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection."));
options->get_popup()->add_item(TTR("Create Simplified Convex Collision Sibling"), MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE);
options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy."));
options->get_popup()->add_item(TTR("Create Multiple Convex Collision Siblings"), MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES); options->get_popup()->add_item(TTR("Create Multiple Convex Collision Siblings"), MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES);
options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between the two above options.")); options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision."));
options->get_popup()->add_separator(); options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("Create Navigation Mesh"), MENU_OPTION_CREATE_NAVMESH); options->get_popup()->add_item(TTR("Create Navigation Mesh"), MENU_OPTION_CREATE_NAVMESH);
options->get_popup()->add_separator(); options->get_popup()->add_separator();

View file

@ -43,6 +43,7 @@ class MeshInstance3DEditor : public Control {
MENU_OPTION_CREATE_STATIC_TRIMESH_BODY, MENU_OPTION_CREATE_STATIC_TRIMESH_BODY,
MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE, MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE,
MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE, MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE,
MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE,
MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES, MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES,
MENU_OPTION_CREATE_NAVMESH, MENU_OPTION_CREATE_NAVMESH,
MENU_OPTION_CREATE_OUTLINE_MESH, MENU_OPTION_CREATE_OUTLINE_MESH,

View file

@ -32,7 +32,7 @@
#include "scene/resources/mesh.h" #include "scene/resources/mesh.h"
#include "thirdparty/vhacd/public/VHACD.h" #include "thirdparty/vhacd/public/VHACD.h"
static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &p_faces) { static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &p_faces, int p_max_convex_hulls = -1) {
Vector<float> vertices; Vector<float> vertices;
vertices.resize(p_faces.size() * 9); vertices.resize(p_faces.size() * 9);
Vector<uint32_t> indices; Vector<uint32_t> indices;
@ -47,8 +47,12 @@ static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &p_faces) {
} }
} }
VHACD::IVHACD *decomposer = VHACD::CreateVHACD();
VHACD::IVHACD::Parameters params; 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); decomposer->Compute(vertices.ptr(), vertices.size() / 3, indices.ptr(), indices.size() / 3, params);
int hull_count = decomposer->GetNConvexHulls(); int hull_count = decomposer->GetNConvexHulls();

View file

@ -241,12 +241,12 @@ void MeshInstance3D::create_trimesh_collision() {
} }
} }
Node *MeshInstance3D::create_convex_collision_node() { Node *MeshInstance3D::create_convex_collision_node(bool p_clean, bool p_simplify) {
if (mesh.is_null()) { if (mesh.is_null()) {
return nullptr; return nullptr;
} }
Ref<Shape3D> shape = mesh->create_convex_shape(); Ref<Shape3D> shape = mesh->create_convex_shape(p_clean, p_simplify);
if (shape.is_null()) { if (shape.is_null()) {
return nullptr; return nullptr;
} }
@ -258,8 +258,8 @@ Node *MeshInstance3D::create_convex_collision_node() {
return static_body; return static_body;
} }
void MeshInstance3D::create_convex_collision() { void MeshInstance3D::create_convex_collision(bool p_clean, bool p_simplify) {
StaticBody3D *static_body = Object::cast_to<StaticBody3D>(create_convex_collision_node()); StaticBody3D *static_body = Object::cast_to<StaticBody3D>(create_convex_collision_node(p_clean, p_simplify));
ERR_FAIL_COND(!static_body); ERR_FAIL_COND(!static_body);
static_body->set_name(String(get_name()) + "_col"); static_body->set_name(String(get_name()) + "_col");
@ -451,7 +451,7 @@ void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_trimesh_collision"), &MeshInstance3D::create_trimesh_collision); ClassDB::bind_method(D_METHOD("create_trimesh_collision"), &MeshInstance3D::create_trimesh_collision);
ClassDB::set_method_flags("MeshInstance3D", "create_trimesh_collision", METHOD_FLAGS_DEFAULT); ClassDB::set_method_flags("MeshInstance3D", "create_trimesh_collision", METHOD_FLAGS_DEFAULT);
ClassDB::bind_method(D_METHOD("create_convex_collision"), &MeshInstance3D::create_convex_collision); ClassDB::bind_method(D_METHOD("create_convex_collision", "clean", "simplify"), &MeshInstance3D::create_convex_collision, DEFVAL(true), DEFVAL(false));
ClassDB::set_method_flags("MeshInstance3D", "create_convex_collision", METHOD_FLAGS_DEFAULT); ClassDB::set_method_flags("MeshInstance3D", "create_convex_collision", METHOD_FLAGS_DEFAULT);
ClassDB::bind_method(D_METHOD("create_multiple_convex_collisions"), &MeshInstance3D::create_multiple_convex_collisions); ClassDB::bind_method(D_METHOD("create_multiple_convex_collisions"), &MeshInstance3D::create_multiple_convex_collisions);
ClassDB::set_method_flags("MeshInstance3D", "create_multiple_convex_collisions", METHOD_FLAGS_DEFAULT); ClassDB::set_method_flags("MeshInstance3D", "create_multiple_convex_collisions", METHOD_FLAGS_DEFAULT);

View file

@ -83,8 +83,8 @@ public:
Node *create_trimesh_collision_node(); Node *create_trimesh_collision_node();
void create_trimesh_collision(); void create_trimesh_collision();
Node *create_convex_collision_node(); Node *create_convex_collision_node(bool p_clean = true, bool p_simplify = false);
void create_convex_collision(); void create_convex_collision(bool p_clean = true, bool p_simplify = false);
Node *create_multiple_convex_collisions_node(); Node *create_multiple_convex_collisions_node();
void create_multiple_convex_collisions(); void create_multiple_convex_collisions();

View file

@ -30,6 +30,7 @@
#include "mesh.h" #include "mesh.h"
#include "core/math/convex_hull.h"
#include "core/templates/pair.h" #include "core/templates/pair.h"
#include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/concave_polygon_shape_3d.h"
#include "scene/resources/convex_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_3d.h"
@ -221,9 +222,17 @@ Vector<Face3> Mesh::get_faces() const {
*/ */
} }
Ref<Shape3D> Mesh::create_convex_shape() const { Ref<Shape3D> Mesh::create_convex_shape(bool p_clean, bool p_simplify) const {
Vector<Vector3> vertices; if (p_simplify) {
Vector<Ref<Shape3D>> decomposed = convex_decompose(1);
if (decomposed.size() == 1) {
return decomposed[0];
} else {
ERR_PRINT("Convex shape simplification failed, falling back to simpler process.");
}
}
Vector<Vector3> vertices;
for (int i = 0; i < get_surface_count(); i++) { for (int i = 0; i < get_surface_count(); i++) {
Array a = surface_get_arrays(i); Array a = surface_get_arrays(i);
ERR_FAIL_COND_V(a.is_empty(), Ref<ConvexPolygonShape3D>()); ERR_FAIL_COND_V(a.is_empty(), Ref<ConvexPolygonShape3D>());
@ -232,6 +241,18 @@ Ref<Shape3D> Mesh::create_convex_shape() const {
} }
Ref<ConvexPolygonShape3D> shape = memnew(ConvexPolygonShape3D); Ref<ConvexPolygonShape3D> shape = memnew(ConvexPolygonShape3D);
if (p_clean) {
Geometry3D::MeshData md;
Error err = ConvexHullComputer::convex_hull(vertices, md);
if (err == OK) {
shape->set_points(md.vertices);
return shape;
} else {
ERR_PRINT("Convex shape cleaning failed, falling back to simpler process.");
}
}
shape->set_points(vertices); shape->set_points(vertices);
return shape; return shape;
} }
@ -543,12 +564,12 @@ void Mesh::clear_cache() const {
debug_lines.clear(); debug_lines.clear();
} }
Vector<Ref<Shape3D>> Mesh::convex_decompose() const { Vector<Ref<Shape3D>> Mesh::convex_decompose(int p_max_convex_hulls) const {
ERR_FAIL_COND_V(!convex_composition_function, Vector<Ref<Shape3D>>()); ERR_FAIL_COND_V(!convex_composition_function, Vector<Ref<Shape3D>>());
const Vector<Face3> faces = get_faces(); const Vector<Face3> faces = get_faces();
Vector<Vector<Face3>> decomposed = convex_composition_function(faces); Vector<Vector<Face3>> decomposed = convex_composition_function(faces, p_max_convex_hulls);
Vector<Ref<Shape3D>> ret; Vector<Ref<Shape3D>> ret;
@ -1852,7 +1873,7 @@ void ArrayMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("surface_set_name", "surf_idx", "name"), &ArrayMesh::surface_set_name); ClassDB::bind_method(D_METHOD("surface_set_name", "surf_idx", "name"), &ArrayMesh::surface_set_name);
ClassDB::bind_method(D_METHOD("surface_get_name", "surf_idx"), &ArrayMesh::surface_get_name); ClassDB::bind_method(D_METHOD("surface_get_name", "surf_idx"), &ArrayMesh::surface_get_name);
ClassDB::bind_method(D_METHOD("create_trimesh_shape"), &ArrayMesh::create_trimesh_shape); ClassDB::bind_method(D_METHOD("create_trimesh_shape"), &ArrayMesh::create_trimesh_shape);
ClassDB::bind_method(D_METHOD("create_convex_shape"), &ArrayMesh::create_convex_shape); ClassDB::bind_method(D_METHOD("create_convex_shape", "clean", "simplify"), &ArrayMesh::create_convex_shape, DEFVAL(true), DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_outline", "margin"), &ArrayMesh::create_outline); ClassDB::bind_method(D_METHOD("create_outline", "margin"), &ArrayMesh::create_outline);
ClassDB::bind_method(D_METHOD("regen_normal_maps"), &ArrayMesh::regen_normal_maps); ClassDB::bind_method(D_METHOD("regen_normal_maps"), &ArrayMesh::regen_normal_maps);
ClassDB::set_method_flags(get_class_static(), _scs_create("regen_normal_maps"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); ClassDB::set_method_flags(get_class_static(), _scs_create("regen_normal_maps"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);

View file

@ -149,7 +149,7 @@ public:
void generate_debug_mesh_indices(Vector<Vector3> &r_points); void generate_debug_mesh_indices(Vector<Vector3> &r_points);
Ref<Shape3D> create_trimesh_shape() const; Ref<Shape3D> create_trimesh_shape() const;
Ref<Shape3D> create_convex_shape() const; Ref<Shape3D> create_convex_shape(bool p_clean = true, bool p_simplify = false) const;
Ref<Mesh> create_outline(float p_margin) const; Ref<Mesh> create_outline(float p_margin) const;
@ -159,11 +159,11 @@ public:
Size2i get_lightmap_size_hint() const; Size2i get_lightmap_size_hint() const;
void clear_cache() const; void clear_cache() const;
typedef Vector<Vector<Face3>> (*ConvexDecompositionFunc)(const Vector<Face3> &); typedef Vector<Vector<Face3>> (*ConvexDecompositionFunc)(const Vector<Face3> &p_faces, int p_max_convex_hulls);
static ConvexDecompositionFunc convex_composition_function; static ConvexDecompositionFunc convex_composition_function;
Vector<Ref<Shape3D>> convex_decompose() const; Vector<Ref<Shape3D>> convex_decompose(int p_max_convex_hulls = -1) const;
virtual int get_builtin_bind_pose_count() const; virtual int get_builtin_bind_pose_count() const;
virtual Transform3D get_builtin_bind_pose(int p_index) const; virtual Transform3D get_builtin_bind_pose(int p_index) const;