Add csg boolean operators using elalish/manifold.

Uses MeshGL64 for more floating point precision.

Co-Authored-By: 31 <31eee384@gmail.com>
Co-Authored-By: Claudio Z <120678869+cloudofoz@users.noreply.github.com>
This commit is contained in:
K. S. Ernest (iFire) Lee
2022-08-03 12:14:42 -07:00
parent d09d82d433
commit fda444bb01
45 changed files with 20005 additions and 1628 deletions

View File

@ -32,6 +32,8 @@
#include "core/math/geometry_2d.h"
#include <manifold/manifold.h>
void CSGShape3D::set_use_collision(bool p_enable) {
if (use_collision == p_enable) {
return;
@ -167,78 +169,213 @@ void CSGShape3D::_make_dirty(bool p_parent_removing) {
dirty = true;
}
CSGBrush *CSGShape3D::_get_brush() {
if (dirty) {
if (brush) {
memdelete(brush);
}
brush = nullptr;
enum ManifoldProperty {
MANIFOLD_PROPERTY_POSITION_X = 0,
MANIFOLD_PROPERTY_POSITION_Y,
MANIFOLD_PROPERTY_POSITION_Z,
MANIFOLD_PROPERTY_INVERT,
MANIFOLD_PROPERTY_SMOOTH_GROUP,
MANIFOLD_PROPERTY_UV_X_0,
MANIFOLD_PROPERTY_UV_Y_0,
MANIFOLD_PROPERTY_MAX
};
CSGBrush *n = _build_brush();
static void _unpack_manifold(
const manifold::Manifold &p_manifold,
const HashMap<int32_t, Ref<Material>> &p_mesh_materials,
CSGBrush *r_mesh_merge) {
manifold::MeshGL64 mesh = p_manifold.GetMeshGL64();
for (int i = 0; i < get_child_count(); i++) {
CSGShape3D *child = Object::cast_to<CSGShape3D>(get_child(i));
if (!child) {
continue;
}
if (!child->is_visible()) {
continue;
}
constexpr int32_t order[3] = { 0, 2, 1 };
CSGBrush *n2 = child->_get_brush();
if (!n2) {
continue;
}
if (!n) {
n = memnew(CSGBrush);
n->copy_from(*n2, child->get_transform());
} else {
CSGBrush *nn = memnew(CSGBrush);
CSGBrush *nn2 = memnew(CSGBrush);
nn2->copy_from(*n2, child->get_transform());
CSGBrushOperation bop;
switch (child->get_operation()) {
case CSGShape3D::OPERATION_UNION:
bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap);
break;
case CSGShape3D::OPERATION_INTERSECTION:
bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap);
break;
case CSGShape3D::OPERATION_SUBTRACTION:
bop.merge_brushes(CSGBrushOperation::OPERATION_SUBTRACTION, *n, *nn2, *nn, snap);
break;
}
memdelete(n);
memdelete(nn2);
n = nn;
}
for (size_t run_i = 0; run_i < mesh.runIndex.size() - 1; run_i++) {
uint32_t original_id = -1;
if (run_i < mesh.runOriginalID.size()) {
original_id = mesh.runOriginalID[run_i];
}
if (n) {
AABB aabb;
for (int i = 0; i < n->faces.size(); i++) {
for (int j = 0; j < 3; j++) {
if (i == 0 && j == 0) {
aabb.position = n->faces[i].vertices[j];
} else {
aabb.expand_to(n->faces[i].vertices[j]);
}
}
}
node_aabb = aabb;
} else {
node_aabb = AABB();
Ref<Material> material;
if (p_mesh_materials.has(original_id)) {
material = p_mesh_materials[original_id];
}
// Find or reserve a material ID in the brush.
int32_t material_id = r_mesh_merge->materials.find(material);
if (material_id == -1) {
material_id = r_mesh_merge->materials.size();
r_mesh_merge->materials.push_back(material);
}
brush = n;
size_t begin = mesh.runIndex[run_i];
size_t end = mesh.runIndex[run_i + 1];
for (size_t vert_i = begin; vert_i < end; vert_i += 3) {
CSGBrush::Face face;
face.material = material_id;
int32_t first_property_index = mesh.triVerts[vert_i + order[0]];
face.smooth = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_SMOOTH_GROUP] > 0.5f;
face.invert = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_INVERT] > 0.5f;
dirty = false;
for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) {
int32_t property_i = mesh.triVerts[vert_i + order[tri_order_i]];
ERR_FAIL_COND_MSG(property_i * mesh.numProp >= mesh.vertProperties.size(), "Invalid index into vertex properties");
face.vertices[tri_order_i] = Vector3(
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_X],
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Y],
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Z]);
face.uvs[tri_order_i] = Vector2(
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_X_0],
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_Y_0]);
}
r_mesh_merge->faces.push_back(face);
}
}
r_mesh_merge->_regen_face_aabbs();
}
static void _pack_manifold(
const CSGBrush *const p_mesh_merge,
manifold::Manifold &r_manifold,
HashMap<int32_t, Ref<Material>> &p_mesh_materials,
float p_snap) {
ERR_FAIL_NULL_MSG(p_mesh_merge, "p_mesh_merge is null");
HashMap<uint32_t, Vector<CSGBrush::Face>> faces_by_material;
for (int face_i = 0; face_i < p_mesh_merge->faces.size(); face_i++) {
const CSGBrush::Face &face = p_mesh_merge->faces[face_i];
faces_by_material[face.material].push_back(face);
}
manifold::MeshGL64 mesh;
mesh.tolerance = p_snap;
mesh.numProp = MANIFOLD_PROPERTY_MAX;
mesh.runOriginalID.reserve(faces_by_material.size());
mesh.runIndex.reserve(faces_by_material.size() + 1);
mesh.vertProperties.reserve(p_mesh_merge->faces.size() * 3 * MANIFOLD_PROPERTY_MAX);
// Make a run of triangles for each material.
for (const KeyValue<uint32_t, Vector<CSGBrush::Face>> &E : faces_by_material) {
const uint32_t material_id = E.key;
const Vector<CSGBrush::Face> &faces = E.value;
mesh.runIndex.push_back(mesh.triVerts.size());
// Associate the material with an ID.
uint32_t reserved_id = r_manifold.ReserveIDs(1);
mesh.runOriginalID.push_back(reserved_id);
Ref<Material> material;
if (material_id < p_mesh_merge->materials.size()) {
material = p_mesh_merge->materials[material_id];
}
p_mesh_materials.insert(reserved_id, material);
for (const CSGBrush::Face &face : faces) {
for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) {
constexpr int32_t order[3] = { 0, 2, 1 };
int i = order[tri_order_i];
mesh.triVerts.push_back(mesh.vertProperties.size() / MANIFOLD_PROPERTY_MAX);
size_t begin = mesh.vertProperties.size();
mesh.vertProperties.resize(mesh.vertProperties.size() + MANIFOLD_PROPERTY_MAX);
// Add the vertex properties.
// Use CSGBrush constants rather than push_back for clarity.
double *vert = &mesh.vertProperties[begin];
vert[MANIFOLD_PROPERTY_POSITION_X] = face.vertices[i].x;
vert[MANIFOLD_PROPERTY_POSITION_Y] = face.vertices[i].y;
vert[MANIFOLD_PROPERTY_POSITION_Z] = face.vertices[i].z;
vert[MANIFOLD_PROPERTY_UV_X_0] = face.uvs[i].x;
vert[MANIFOLD_PROPERTY_UV_Y_0] = face.uvs[i].y;
vert[MANIFOLD_PROPERTY_SMOOTH_GROUP] = face.smooth ? 1.0f : 0.0f;
vert[MANIFOLD_PROPERTY_INVERT] = face.invert ? 1.0f : 0.0f;
}
}
}
// runIndex needs an explicit end value.
mesh.runIndex.push_back(mesh.triVerts.size());
ERR_FAIL_COND_MSG(mesh.vertProperties.size() % mesh.numProp != 0, "Invalid vertex properties size.");
mesh.Merge();
r_manifold = manifold::Manifold(mesh);
manifold::Manifold::Error err = r_manifold.Status();
if (err != manifold::Manifold::Error::NoError) {
print_error(String("Manifold creation from mesh failed:" + itos((int)err)));
}
}
struct ManifoldOperation {
manifold::Manifold manifold;
manifold::OpType operation;
static manifold::OpType convert_csg_op(CSGShape3D::Operation op) {
switch (op) {
case CSGShape3D::OPERATION_SUBTRACTION:
return manifold::OpType::Subtract;
case CSGShape3D::OPERATION_INTERSECTION:
return manifold::OpType::Intersect;
default:
return manifold::OpType::Add;
}
}
ManifoldOperation() :
operation(manifold::OpType::Add) {}
ManifoldOperation(const manifold::Manifold &m, manifold::OpType op) :
manifold(m), operation(op) {}
};
CSGBrush *CSGShape3D::_get_brush() {
if (!dirty) {
return brush;
}
if (brush) {
memdelete(brush);
}
brush = nullptr;
CSGBrush *n = _build_brush();
HashMap<int32_t, Ref<Material>> mesh_materials;
manifold::Manifold root_manifold;
_pack_manifold(n, root_manifold, mesh_materials, get_snap());
manifold::OpType current_op = ManifoldOperation::convert_csg_op(get_operation());
std::vector<manifold::Manifold> manifolds;
manifolds.push_back(root_manifold);
for (int i = 0; i < get_child_count(); i++) {
CSGShape3D *child = Object::cast_to<CSGShape3D>(get_child(i));
if (!child || !child->is_visible()) {
continue;
}
CSGBrush *child_brush = child->_get_brush();
if (!child_brush) {
continue;
}
CSGBrush transformed_brush;
transformed_brush.copy_from(*child_brush, child->get_transform());
manifold::Manifold child_manifold;
_pack_manifold(&transformed_brush, child_manifold, mesh_materials, get_snap());
manifold::OpType child_operation = ManifoldOperation::convert_csg_op(child->get_operation());
if (child_operation != current_op) {
manifold::Manifold result = manifold::Manifold::BatchBoolean(manifolds, current_op);
manifolds.clear();
manifolds.push_back(result);
current_op = child_operation;
}
manifolds.push_back(child_manifold);
}
if (!manifolds.empty()) {
manifold::Manifold manifold_result = manifold::Manifold::BatchBoolean(manifolds, current_op);
if (n) {
memdelete(n);
}
n = memnew(CSGBrush);
_unpack_manifold(manifold_result, mesh_materials, n);
}
AABB aabb;
if (n && !n->faces.is_empty()) {
aabb.position = n->faces[0].vertices[0];
for (const CSGBrush::Face &face : n->faces) {
for (int i = 0; i < 3; ++i) {
aabb.expand_to(face.vertices[i]);
}
}
}
node_aabb = aabb;
brush = n;
dirty = false;
return brush;
}