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

@ -6,7 +6,42 @@ Import("env_modules")
env_csg = env_modules.Clone()
# Godot source files
env_csg.Append(CPPDEFINES=("MANIFOLD_PAR", "-1"))
# Thirdparty source files
thirdparty_obj = []
thirdparty_dir = "#thirdparty/manifold/"
thirdparty_sources = [
"src/boolean_result.cpp",
"src/boolean3.cpp",
"src/constructors.cpp",
"src/csg_tree.cpp",
"src/edge_op.cpp",
"src/face_op.cpp",
"src/impl.cpp",
"src/manifold.cpp",
"src/polygon.cpp",
"src/properties.cpp",
"src/quickhull.cpp",
"src/smoothing.cpp",
"src/sort.cpp",
"src/subdivision.cpp",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_csg.Prepend(
CPPPATH=[
thirdparty_dir + "include",
]
)
env_thirdparty = env_csg.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
# Godot's own source files
env_csg.add_source_files(env.modules_sources, "*.cpp")
if env.editor_build:
env_csg.add_source_files(env.modules_sources, "editor/*.cpp")

File diff suppressed because it is too large Load diff

View file

@ -55,150 +55,18 @@ struct CSGBrush {
Vector<Face> faces;
Vector<Ref<Material>> materials;
inline void _regen_face_aabbs();
inline void _regen_face_aabbs() {
for (int i = 0; i < faces.size(); i++) {
faces.write[i].aabb = AABB();
faces.write[i].aabb.position = faces[i].vertices[0];
faces.write[i].aabb.expand_to(faces[i].vertices[1]);
faces.write[i].aabb.expand_to(faces[i].vertices[2]);
}
}
// Create a brush from faces.
void build_from_faces(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials, const Vector<bool> &p_invert_faces);
void copy_from(const CSGBrush &p_brush, const Transform3D &p_xform);
};
struct CSGBrushOperation {
enum Operation {
OPERATION_UNION,
OPERATION_INTERSECTION,
OPERATION_SUBTRACTION,
};
void merge_brushes(Operation p_operation, const CSGBrush &p_brush_a, const CSGBrush &p_brush_b, CSGBrush &r_merged_brush, float p_vertex_snap);
struct MeshMerge {
struct Face {
bool from_b = false;
bool inside = false;
int points[3] = {};
Vector2 uvs[3];
bool smooth = false;
bool invert = false;
int material_idx = 0;
};
struct FaceBVH {
int face = 0;
int left = 0;
int right = 0;
int next = 0;
Vector3 center;
AABB aabb;
};
struct FaceBVHCmpX {
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
return p_left->center.x < p_right->center.x;
}
};
struct FaceBVHCmpY {
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
return p_left->center.y < p_right->center.y;
}
};
struct FaceBVHCmpZ {
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
return p_left->center.z < p_right->center.z;
}
};
struct VertexKey {
int32_t x, y, z;
_FORCE_INLINE_ bool operator<(const VertexKey &p_key) const {
if (x == p_key.x) {
if (y == p_key.y) {
return z < p_key.z;
} else {
return y < p_key.y;
}
} else {
return x < p_key.x;
}
}
_FORCE_INLINE_ bool operator==(const VertexKey &p_key) const {
return (x == p_key.x && y == p_key.y && z == p_key.z);
}
};
struct VertexKeyHash {
static _FORCE_INLINE_ uint32_t hash(const VertexKey &p_vk) {
uint32_t h = hash_murmur3_one_32(p_vk.x);
h = hash_murmur3_one_32(p_vk.y, h);
h = hash_murmur3_one_32(p_vk.z, h);
return h;
}
};
struct Intersection {
bool found = false;
real_t conormal = FLT_MAX;
real_t distance_squared = FLT_MAX;
real_t origin_angle = FLT_MAX;
};
struct IntersectionDistance {
bool is_conormal;
real_t distance_squared;
};
Vector<Vector3> points;
Vector<Face> faces;
HashMap<Ref<Material>, int> materials;
HashMap<Vector3, int> vertex_map;
OAHashMap<VertexKey, int, VertexKeyHash> snap_cache;
float vertex_snap = 0.0;
inline void _add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance, bool p_is_conormal) const;
inline bool _bvh_inside(FaceBVH *r_facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const;
inline int _create_bvh(FaceBVH *r_facebvhptr, FaceBVH **r_facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc);
void add_face(const Vector3 p_points[3], const Vector2 p_uvs[3], bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b);
void mark_inside_faces();
};
struct Build2DFaces {
struct Vertex2D {
Vector2 point;
Vector2 uv;
};
struct Face2D {
int vertex_idx[3] = {};
};
Vector<Vertex2D> vertices;
Vector<Face2D> faces;
Plane plane;
Transform3D to_2D;
Transform3D to_3D;
float vertex_snap2 = 0.0;
inline int _get_point_idx(const Vector2 &p_point);
inline int _add_vertex(const Vertex2D &p_vertex);
inline void _add_vertex_idx_sorted(Vector<int> &r_vertex_indices, int p_new_vertex_index);
inline void _merge_faces(const Vector<int> &p_segment_indices);
inline void _find_edge_intersections(const Vector2 p_segment_points[2], Vector<int> &r_segment_indices);
inline int _insert_point(const Vector2 &p_point);
void insert(const CSGBrush &p_brush, int p_brush_face);
void addFacesToMesh(MeshMerge &r_mesh_merge, bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b);
Build2DFaces() {}
Build2DFaces(const CSGBrush &p_brush, int p_brush_face, float p_vertex_snap2);
};
struct Build2DFaceCollection {
HashMap<int, Build2DFaces> build2DFacesA;
HashMap<int, Build2DFaces> build2DFacesB;
};
void update_faces(const CSGBrush &p_brush_a, const int p_face_idx_a, const CSGBrush &p_brush_b, const int p_face_idx_b, Build2DFaceCollection &p_collection, float p_vertex_snap);
};
#endif // CSG_H

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;
}